BEROUNOS for AI-native devices

Apps

A page, a manifest, a service.

Everything on the launcher is described by one manifest. Write the page, say what it needs, and the system does the rest.

A Beroun app is a folder with a manifest.json. The page is plain HTML, CSS and JavaScript, the manifest says what the app needs, and the system supplies the window, the saved state, the services, the dialogs, and the way for Caroline to drive it.

What an app is

The apps that ship with the system live in apps/<id>/ and are served at /apps/<id>/…; apps installed from the store live in the apps directory. The system's own views (Caroline's chat, her voice view, the first-run wizard) use the same format in system/<id>/view.json. They are never apps and can't be moved or removed.

KindHow it runs
Web app, shared window "window": "shared"Its page is shown in the launcher's web view, one at a time. It keeps its state. This is what most apps are
Web app, own window (the default)A web view of its own
Native native.cmdA program of its own, such as the Browser
Hidden tile.hiddenLaunched by the system with no tile

Any app may also declare services, endpoints, a big page for an external display, permissions, capabilities, a caroline section and agents.

The manifest

This is a real manifest: the Calendar, with a background service, an endpoint and tools for Caroline.

apps/calendar/manifest.json
{ "format": 1, "id": "calendar", "name": "Calendar", "version": "1.0.0", "icon": "icon.svg", "frontend": { "entry": "index.html", "window": "shared" }, "state": true, // the contract: comes back where it was left "services": [{ "id": "calendard", "host": "process", "cmd": ["python3", "{app}/calendard.py", "{port}"], "start": "system", "priority": 70, "memory_mb": 20, "sleep": true }], "endpoints": [{ "path": "events", "methods": ["GET", "POST"], "service": "calendard" }], "caroline": { "description": "Calendar with events and a task list", "tools": [{ "name": "show_date", "description": "Shows a day", "input": { "date": "string" } }] } }
FieldMeaning
formatAlways 1
id, name, version, iconThe folder name (lowercase, digits, -), the title on the tile, x.y.z, the tile's picture
frontendentry (a file of the app) or url; window: shared or own; optionally microphone and transparent
nativeInstead of frontend: {"cmd": [...]}. Exactly one of the two
tileorder (smaller is earlier) and hidden
stateThe state contract. A shared-window frontend must set it to true and use Platform.state; the checker verifies it
opensFile types the app opens when the file manager chooses a file: ["txt"]
services, endpointsBackends that outlive the window, and the paths the shell passes through to them (Services)
carolineHow the assistant drives the app: description, skills, mcp, tools
agentsSpecialists the app publishes (Agents & A2A)
externalA big page for an external display (System API)
permissionsWhat the app may ask of the system: caroline.ask, apps.manage, account.session. The manifest is the approval
capabilitiesWhich device features it uses: Camera, Microphone, GPS, Bluetooth, NFC, Notification, FileStorage, CloudStorage, Network, ExternalDisplay. Mark one "scope": {"required": true} and the store won't offer the app on a device without it
berounThe lowest OS version the app needs. Required in store packages
systemtrue for system apps: the store can only update them, never remove them

Unknown fields are errors. There are no "extra" keys to try: if a field isn't in the format, the shell refuses the manifest and the manifest test fails.

The state contract

A shared-window app sets "state": true, uses Platform.state and Platform.onClose, and comes back to the same place when it is opened again: the open file, the folder, the conversation, the scroll position. It must save before calling Platform.exit(), because closing is immediate.

Platform.onClose(async () => Platform.state.set({ file, scroll })); // MODE or another app opening const saved = await Platform.state.get(); // the object, or null the first time
Good to know

onClose has about two seconds. A page that is still busy then is closed anyway, so save small and save early. If an app is covered by another (an external display's big page, for instance), Platform.onSuspend gets the same two seconds.

The Platform API

Every page includes <script src="/platform.js">. It gives the page the whole system through one object.

AreaCalls
LifecyclePlatform.state.get()/set(obj), Platform.onClose(fn), Platform.exit()
TranslationPlatform.t(key, {placeholders}), one key per phrase in every language file
Dialogs and signalsPlatform.messageBox(opts), Platform.notify(text, opts), Platform.sound.play(name), Platform.vibra.pulse(pattern)
NotificationsPlatform.notifications and .on(fn)
External displayPlatform.display.*
CarolinePlatform.tools.handle(name, fn) answers her tool calls; with caroline.ask an app may ask her something (POST /api/caroline/ask)
AgentsPlatform.sw.call(command, params, agent?): an agent's call of the owner's Camerlengo service through the shell
FilesPlatform.files.* over /api/fs/*, the memory card only
Dialogs, sound and vibration in one place
// ask the owner; one of four standard sets of buttons, or your own (1 to 4) const r = await Platform.messageBox({ title: 'Delete note?', text: 'It will be removed from the memory card.', kind: 'yesno', icon: 'question' }); if (r.button === 'Yes') await deleteNote(); Platform.sound.play('ok'); Platform.vibra.pulse('double'); Platform.notify('Saved', { level: 'info' });

The full reference for these calls is on the System API page.

The logical input model

The interface is driven by a gamepad-style input set plus touch: a D-pad, sticks, A B X Y, L1 L2 R1 R2, START SELECT, MODE and POWER. How a physical control produces a logical button is the platform's business; apps only see these.

640 × 480touch L2 volume + L1 volume − R2 Caroline R1 cursor X Y A B SELECT MODE START ⏻POWER Free for apps System: never reaches an app No job until the owner decides
The logical buttons. A and B mean Enter and Escape.
  • Free for apps: the cursor (D-pad), A (Enter) and B (Escape or back; at the top level of an app it leaves the app).
  • The system's: MODE always leaves the app. L1/L2 are volume. POWER sleeps on a short press and powers off on a long one. R2 opens Caroline's voice dialogue. No app sees these.
  • Reserved: X, Y, R1, START and SELECT have no job in any app. SELECT moves the focus between screens when an external display is connected.
  • With the on-screen keyboard open, B deletes a character while the field has text. On an empty field it reaches the page as Escape, so treat Escape in a field as "leave the field", then "back". Y hides the keyboard.
  • Left and Right page through long lists where a screen has no other use for them. A finger drag scrolls everywhere.

Design every screen as a list or a set of buttons the cursor moves between and A activates. Where you might reach for a shortcut key, make a menu.

Calculator with a highlighted key
Cursor-driven: the highlighted key is activated with A.
The Files app
Lists of rows work the same way.
Notepad
With a text field, the on-screen keyboard appears.

The apps that ship with Beroun

AppNotes
CarolineThe first tile: her text chat. Part of the system
Browser (system)Native browser on WPE with tabs
Settings (system)Network, accounts (the owner's and Caroline's), date and time, do not disturb, vibration, automatic app updates, agents, About
Wi-Fi (system)Scan and connect through the network interface
Ratatosk (system)An end-to-end encrypted messenger with groups, attachments and audio/video calls
Store (system)Install, update and remove apps
CommunicatorMail, WhatsApp, Telegram, Signal and Slack in one style
Files, Notepad, CalculatorFile manager (opens maps file types to apps), plain-text notes, calculator
Music, VideoPlayers that remember their place
Calendar, ClockServices that ring in sleep
Games and MinecraftKnights & Ladies, Pirates, Quidditch; a client of the cloud Minecraft

System apps can be updated by the store but never removed. Apps that ship with the system without that flag are preinstalled: the store can hide and restore them, and their data stays. Everything else is from the store.

Caroline's chat
Caroline
The Communicator
Communicator
The Calendar
Calendar
The Clock
Clock
The Browser
Browser
The Music player
Music

Caroline drives apps

An app's caroline section turns it into a group of tools for the assistant, named app-<id>.

KeyWhat it gives her
descriptionRequired. One line for her list of groups
toolsPage tools, answered by Platform.tools.handle(name, fn). The app is opened first when she calls one
skillsFolders with a SKILL.md that join her skill catalog: instructions for using the app well
mcpMCP servers of the app (stdio or http); {app} and {port} are filled in

An app without the section is still reachable: Caroline has a group of system tools (beroun_*) that can list, open and close apps, read a page, press a key and type text in any app.

A tool in the page, from the Calculator
Platform.tools.handle('get_display', async () => display()); Platform.tools.handle('calculate', async args => { const src = String(args.expression || '').replace(/\s+/g, ''); press('C'); // the same key presses a finger would make for (const ch of src) press(ch); press('EQ'); draw(); return display(); // the answer goes back to Caroline });

Every page also answers dom_text, dom_tree, dom_click, dom_type and dom_key, which is how she reads, presses and types in an app that declares nothing.

Writing a new app

Create the folder. apps/<id>/ with manifest.json, index.html and icon.svg. Set tile.order. Include /platform.js.

Save and restore state with Platform.state. Use only the cursor, A and B.

Use the system's dialogs. Platform.messageBox, notify and sound instead of your own, and every visible phrase through Platform.t.

Declare only what you use. permissions, capabilities (mark the required ones), and beroun, the lowest OS version.

Give Caroline a way in. A caroline section, at least a description, so she can drive the app.

Check it. Run the manifest test and the app's own test. To distribute, see Store & packages.