🚧 Glyx is pre-release software. APIs may change before v1.0. Get started →
Documentation
Configuration
Capabilities Reference

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.txt is normalised to secrets.txt before matching. .. cannot escape a granted glob.
  • A denied call throws with the exact path and the config key to fix.

delete scoping

ConfigBehaviour
delete omittedFalls 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?: boolean

Unlocks 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?: boolean

Unlocks 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?: boolean

Unlocks clipboard.readText() and clipboard.writeText(string).

Image clipboard read/write is not yet supported.


notification — Desktop notifications

notification?: boolean

Unlocks 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. Use glyx dev --bundle to 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?: boolean

Unlocks 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 (or libappindicator3-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?: boolean

Unlocks 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?: boolean

Unlocks 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 sink
  • handle.seek(secs), handle.setVolume(0–1), handle.getVolume()
  • handle.getTime(), handle.getDuration() (total duration; -1 if 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?: boolean

Requires 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 devices
  • microphone.record(durationMs = 3000, deviceName = null)Promise<string> — records for durationMs milliseconds and resolves with the absolute path to a .wav file
const devices = await microphone.listDevices()
const wavPath = await microphone.record(5000)  // 5s from the default device

On macOS and Windows the OS may show a permission prompt on first use.


video — Video playback

video?: boolean

Unlocks 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?: boolean

Unlocks 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?: boolean

Unlocks 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?: boolean

Unlocks the ai API:

  • ai.embed(text)Float32Array — sentence embedding
  • ai.generate({ prompt, model?, maxTokens? })AsyncIterable<string> — local LLM text generation
  • ai.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?: boolean

Unlocks 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?: boolean

Unlocks 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?: boolean

Unlocks the system API:

  • system.getPlatform()'windows' | 'macos' | 'linux'
  • system.getOsVersion()string
  • system.getDisplays() → display geometry array
  • system.appDataDir() → platform app-data path
  • system.isDarkMode() / system.watchDarkMode(handler)
  • system.getLocale() → BCP-47 locale string
  • system.getMemoryInfo() → total/available RAM

battery — Battery status

battery?: boolean

Unlocks system.battery(){ level: number, charging: boolean, chargeRate?: number }. Returns null on desktop systems without a battery.


storage — App data directory

storage?: boolean

Unlocks 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?: boolean

Unlocks system.onSleep(handler) and system.onWake(handler). Useful for pausing background work or flushing state before the system sleeps.


shell — Open external URLs

shell?: boolean

Enables 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 aboveshell 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?: boolean

Unlocks 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?: boolean

Unlocks 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?: boolean

Unlocks 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?: boolean

Unlocks 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?: boolean

Unlocks 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 exceeds target ms (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