The official app marketplace for Claudette.
This repository is the marketplace. Claudette does not need a server
or an account to read it: a marketplace is a structured git repository,
and the client fetches two files from it over raw.githubusercontent.com.
Add it in Claudette under Settings → Marketplaces as startwind/claudette-apps and it
shows up as its own tab on the Marketplace page.
marketplace.json # the marketplace's name and description
index.json # generated: every app, its versions, its metadata
apps/
<app-name>/ # kebab-case; this is the app's identifier
<major.minor.patch>/ # one directory per released version
manifest.json # the contract — what the app is, what it may ask for
manifest.yaml # optional, the same thing for humans
app.html # the app itself: one document body
README.md # optional, shown next to the app
scripts/build-index.mjs # validates apps/ and regenerates index.json
Two rules make the whole thing work:
- The directory is the truth.
apps/<name>/<version>/manifest.jsonmust carry exactly thatnameand thatversion. An app that disagrees with its own path would install under one name and update under another, so the validator rejects it. - A version is immutable. Fixing a published app means adding
1.0.1, not editing1.0.0— someone already installed that one.
{
"schemaVersion": 1,
"kind": "app",
"name": "unit-converter",
"title": "Unit converter",
"version": "1.0.0",
"description": "Convert length, mass and temperature.",
"category": "utilities",
"icon": "lucide:calculator",
"tags": ["units", "conversion"],
"author": "startwind",
"permissions": []
}html never appears in the manifest — the document lives in app.html
beside it, so there is exactly one copy of it to edit.
Claudette wraps it: it supplies <html>, a reset stylesheet, the theme
tokens and the host bridge. So app.html starts at the content, and it
has to be self-contained — the sandbox runs with
default-src 'none', which means no external scripts, stylesheets,
fonts or images. Inline <style> and <script> are fine; a CDN URL
is not.
Style against the tokens the host sets so an app looks like it belongs
in the window: --bg, --fg, --muted, --muted-fg, --border,
--accent, --accent-fg, --danger, --radius, --font, --mono.
They follow the user's light/dark theme, so an app that uses them needs
no theme code of its own.
An app starts with nothing: no network, no filesystem, no clipboard. It
declares what it needs, and the user ticks each one before it exists —
the in-frame claudette SDK grows a member per granted capability and
no more.
| capability | scope | what it gives the app |
|---|---|---|
ai |
— | claudette.ai.prompt(text, options?) |
http_proxy |
hosts, methods |
claudette.http.fetch(url, init?) |
fs_read |
globs |
claudette.fs.read(path), claudette.fs.exists(path) |
fs_write |
globs |
claudette.fs.write(path, contents) |
clipboard_read |
— | claudette.clipboard.read() |
clipboard_write |
— | claudette.clipboard.write(text) |
notification |
— | claudette.notify(title, body) |
open_url |
schemes |
claudette.openUrl(url) |
file_picker |
— | <input type="file">, drop targets, FileReader |
"permissions": [
{ "capability": "notification", "scope": {} },
{ "capability": "http_proxy", "scope": { "hosts": ["api.example.com"], "methods": ["GET"] } }
]Ask for the narrowest scope that works. claudette.can('fs_write')
answers whether a capability was granted, so an app can render its own
off state instead of throwing.
-
Build it in Claudette and publish it — that writes this exact layout, so publishing is a copy.
-
Or write the files by hand under
apps/<name>/<version>/. -
Run the validator and commit what it generates:
node scripts/build-index.mjs
It fails loudly on a manifest that disagrees with its directory, a missing
app.html, an unknown capability or a scoped capability with an empty scope.
CI runs the same script: --check on every pull request (a stale
index.json fails the build) and a regenerate-and-commit on push to
main. No dependencies, no install step — plain Node 22.