Apps#

An app is someone else's code, running inside Ohm, with a list of what it is allowed to reach. It is a folder with a manifest in it:

apps/
  hello-world/
    ohm.app.json     who it is, what it adds, what it needs
    start.ohm        runs once when the app is switched on
    summarise.ohm    a command
    actions.ohm      another command

apps/ is inside the scripts folder (Settings → Folders says where that is). Ohm scans it on start and whenever you press Rescan in Settings → Apps. There is no registry, no install step and no database that can disagree with the disk: the folders are the list.

Ohm ships one app, Hello World, seeded like any other example. Open apps/hello-world/ in the Scripts tree and read it — it is 40 lines, and copying it is the intended way to start.

What an app can add#

It can How
Put commands in Settings → Apps, runnable with one click commands in the manifest
Run something once when it is switched on onStart
Read and drive pages, call the model, write to the Results panel the capabilities it asks for

Commands are ordinary Ohm, Volt or Bolt scripts. Nothing about an app's code is special; what is special is that it runs with a capability list.

The manifest#

{
  "id": "hello-world",
  "name": "Hello World",
  "version": "1.0.0",
  "description": "One sentence a person reads before trusting it.",
  "author": "You",
  "homepage": "https://github.com/you/hello-world",

  "capabilities": ["page", "ai", "results"],

  "commands": [
    {
      "title": "Summarise this page",
      "icon": "◉",
      "description": "Reads the active tab and puts a summary in Results.",
      "script": "summarise.ohm"
    }
  ],

  "onStart": "start.ohm"
}
Field Required Notes
id yes Lower-case letters, digits and dashes, 3–40 characters. Must equal the folder name.
name yes What is shown.
version yes major.minor.patch.
description no One sentence, in plain words. It is the first thing anyone reads.
author, homepage no Shown in Settings.
capabilities no See below. Omitted means none, which is a real and useful choice.
commands no title and script are required; icon and description are not. The script must be inside the app folder.
onStart no A script run once per start. Keep it quick and quiet.

The manifest is parsed strictly. An unknown capability is an error rather than something quietly dropped — an app asking for filesystem when the capability is files must not end up looking like it asked for nothing at all. A manifest that does not parse still appears in Settings, with the reason, because an app that silently fails to appear is worse than one that says why.

Capabilities#

A script running as part of an app may only call the host functions its capabilities cover.

Capability Risk What it allows
page high See which tabs are open, read any page’s text, structure and form values; click, type and run JavaScript in it.
remote high Send data to an endpoint you configured. Refused entirely while the remote switch is off.
tabs medium Open a page, navigate it, show or hide it, close it. Not read it.
ai medium Send text to the configured model profile and read the reply.
files medium Read and write inside the scripts folder. Nowhere else.
projects medium Read and change prompts, curves, experiments, graphs, specs and documents.
models medium List local models, search Hugging Face, download model files.
library low Read the prompts, functions and view templates saved in Settings.
results low Render templates into the Results panel.

Functions that cannot reach anything — sleep, json.encode, print, the ohm.* concurrency helpers — need no capability at all.

Settings → Apps expands every capability into the exact host functions it permits, and that list is read from the host itself rather than written out by hand, so the page cannot drift away from what is enforced.

How it is enforced#

There is exactly one way out of a script — ScriptHost.call — and a run inside apps/<id>/ carries that app's id. So a capability either appears on the list or the call does not happen:

"hello-world" tried to browser.open() but did not ask for the "tabs" capability
(open and close tabs). Add it to the app's ohm.app.json, or stop the app in
Settings -> Apps.

Two things follow from this that are worth knowing:

  • A stopped app is granted nothing. Stopping is not cosmetic; every call is refused.
  • Finding a page is reading, not opening. browser.active() and browser.tabs() need page, not tabs: an app that only wants to read the page in front of you must not have to ask for the power to open tabs to get hold of it.
  • Capabilities are per-run, from the path. A script you wrote in the scripts folder is not an app and is not restricted. Moving a script into apps/<id>/ restricts it.

Managing apps#

Settings → Apps lists everything installed. For each app:

  • its name, version, author and description, and whether it is running or stopped
  • the capabilities it asks for, worst first, coloured by risk
  • What it can reach — its folder, its menu entries, whether anything runs by itself, and the complete list of host functions it may call
  • Start / Stop — stopping takes its capabilities away immediately
  • Uninstall — the folder is moved to .ohm/trash in the scripts folder, like any other delete, so an uninstall is recoverable
  • a button per command, to run it

Below the list, What the capabilities mean is the same reference as the table above, generated from the host.

Which apps are switched off is the only thing Ohm remembers about them; it lives in settings, so an app you stopped stays stopped across a rescan and a restart.

Publishing#

An app is a folder. Zip it, put it in a git repository, send it to someone — there is no registry and nothing to sign up for. To install one, put the folder in apps/ and press Rescan.

Before you publish:

  • Ask for the fewest capabilities that work. They are the first thing anyone reads, and page means "can read every page you visit while I am running".
  • Say what it does in description, in one sentence.
  • Version it, and change the version when you change the app.
  • Keep onStart fast and quiet. It runs on every launch, and an app that announces itself twice is an app people turn off.
  • Keep every script inside the folder. A command pointing outside it is refused.
  • Fail with a sentence a person can act on. Your error messages are the app's interface too.

apps/README.md, seeded next to the example, is the same standard in short form — it travels with the app folder, which is where someone writing one will look.

From a script#

Apps are managed over the protocol, so a script or another app with the projects capability cannot touch them; only the UI can. The methods are apps.list, apps.refresh, apps.setEnabled, apps.uninstall, apps.run and apps.capabilities — see the protocol reference.