pulsar
EN

The manifest — pulsar.json

One file drives the app config and packaging.

{
  "id": "com.example.myapp",
  "name": "my-app",
  "version": "1.0.0",
  "main": "main.ts",
  "icons": { "linux": "assets/icon.png", "macos": "assets/icon.icns", "windows": "assets/icon.ico" },
  "windows": [
    {
      "label": "main",
      "ui": "ui/main.qml",
      "width": 800,
      "height": 600,
      "title": "My App",
      "minWidth": 400,
      "minHeight": 300,
      "resizable": true,
      "frameless": false,
      "transparent": false,
      "icon": "assets/icon.png"
    }
  ],
  "permissions": [],
  "osPermissions": ["camera", "microphone"],
  "fileAssociations": [{ "ext": "md", "mime": "text/markdown", "name": "Markdown document" }],
  "protocols": [{ "scheme": "myapp" }],
  "singleInstance": true
}
Field Meaning
id Reverse-DNS id; used for per-app data/config/cache directories.
name Display/output name (also the dist/<name> file name).
version App version.
main Backend entry point relative to the manifest (default main.ts).
icons Per-platform icon paths (used by packaging).
windows[] Initial windows: label, ui, width, height, title, minWidth, minHeight, maxWidth, maxHeight, resizable, frameless, transparent, icon.
permissions Capabilities the app may use (see Permissions). Absent = all.
osPermissions Runtime OS permissions to declare in the Android manifest: camera, microphone, location, notifications, storage.
protocols Custom URL schemes (deep links) the app handles.
fileAssociations File types the app opens: ext (without the dot), mime, name.
singleInstance Forward a second launch to the running instance and exit (default false).
ignore Extra paths left out of builds, in .gitignore syntax.

Builds always skip .git, node_modules, dist, build, .cache, .pulsar, .env* and sync.db*, and honour the app's root .gitignore. ignore adds patterns on top of that; a ! entry re-includes a path ignored by .gitignore, but never one of the built-in exclusions.

App derives its config from the first manifest window; explicit options passed to new App({...}) override the manifest.

Transparent windows

"transparent": true on a window clears its background, forces frameless and draws a rounded translucent surface. The blur itself is done by the compositor; for Hyprland:

windowrulev2 = opacity 0.94 0.88,class:^(pulsar-host)$
windowrulev2 = blur,class:^(pulsar-host)$
windowrulev2 = rounding 18,class:^(pulsar-host)$

Check the class with hyprctl clients. Without blur transparency still works, but the frosted look is weaker.

Native menu bar

app.setMenu([
  {
    label: "File",
    items: [
      { id: "new", label: "New task", shortcut: "CmdOrCtrl+N" },
      { separator: true },
      { id: "hide", label: "Hide done", checked: app.store.get("hideDone") === true },
    ],
  },
])

app.on("menu", ({ id }) => {
  if (id === "new") app.store.set("composing", true)
})

The menu is built natively in the macOS global menu bar; Linux and Windows currently ignore it. Call setMenu again to refresh checked states. An item with items becomes a submenu; choosing a leaf emits menu with its id.

Permissions

permissions declares the OS-facing capabilities the app may use. When the field is absent every capability is allowed; when present only the listed ones are, and any other capability call fails with a permission denied error.

{ "permissions": ["fs", "net", "dialog"] }
Permission Grants
fs filesystem reads/writes, stat, rename, trash
net net.fetch, net.request, net.websocket
paths standard per-app directories
shell open URLs in the default application
notify native notifications
dialog native file/message dialogs
screen monitor list
theme system color scheme
system locale/language/timezone
storage the app's JSON storage file
secure OS keychain / secret storage
tray system tray icon and menu
clipboard system clipboard read/write
share OS share sheet
device orientation lock and keep-awake
permissions runtime OS permission prompts
media host-backed media playback

Window management is core and is not gated. Custom capabilities registered by the app are never gated either.

Runtime permission prompts are separate from capabilities. List what the app may ask for in osPermissions and request it at runtime:

import { permissions } from "pulsar"

if (await permissions.request("camera")) startScanner()
const granted = await permissions.check("microphone")

Names: camera, microphone, location, notifications, storage. On Android they are mapped into the manifest and request shows the system prompt; on desktop both calls resolve true.