Developers
Build your own OneKit app.
Open your apps folder in Claude Code and say what you want. It reads a build guide that ships with OneKit and writes the app — a sandboxed tool that shows up in your sidebar, stores data, calls AI, and gives Kit new abilities. No servers, no API keys, no build step.
Requires OneKit (Mac or Windows), an active subscription, and Claude Code.
How it works
Three steps to a working app
OneKit is built for the “tell Claude Code what you want” flow. Turning on developer mode scaffolds a guide and a Claude Code skill into your apps folder, so the model already knows the SDK.
Turn on Apps
In OneKit, open Settings → Apps and flip the toggle. OneKit creates ~/OneKit/apps with a starter app, a build guide, and a Claude Code skill.
Open it in Claude Code
Point Claude Code at ~/OneKit/apps. It auto-loads the guide and the onekit-app skill — the full SDK reference, patterns, and rules.
Describe your app
“Build me a OneKit app that tracks my daily habits.” It writes the folder; the moment the manifest is valid, your app appears in the sidebar and live-reloads on every save.
Anatomy
A folder is an app
Every app is a folder in ~/OneKit/apps with a manifest and a self-contained HTML page. The folder name is the app id (lowercase letters, numbers, dashes).
~/OneKit/apps/
habit-tracker/ # folder name = app id (a-z 0-9 -)
onekit.json # the manifest
index.html # your app (loads the SDK + theme)Load the SDK and the design kit in every page — both are served by OneKit at a reserved path, so there is nothing to install:
<link rel="stylesheet" href="/_onekit/theme.css" />
<script src="/_onekit/sdk.js"></script>Apps run in a sandboxed iframe — opaque origin, no Node, no filesystem, no localStorage. Everything privileged goes through window.onekit, and OneKit enforces it in the main process. The sandbox is the security boundary, so an app can only ever do what you grant it.
The manifest
onekit.json
Describes the app and what it can do. An invalid manifest lists the app with an error in Settings → Apps and never loads.
name | Required. String, ≤ 60 chars. |
icon | Optional emoji, ≤ 8 chars. |
description | Optional, ≤ 200 chars. |
entry | Relative .html path in the folder. Default index.html. |
background | Page loaded headlessly to run chat tools. Default = entry. |
version | Strict semver x.y.z. Default 0.0.1 — bump it to ship updates. |
author | Optional, ≤ 60 chars. Shown when sharing or publishing. |
permissions | Array, max 8, from the six values below. |
tools | Array, max 10. Chat tools Kit can call (see below). |
{
"name": "Habit Tracker",
"icon": "🔥",
"description": "Track daily habits with streaks",
"entry": "index.html",
"version": "0.1.0",
"author": "Your Name",
"permissions": ["notifications"],
"tools": [
{
"name": "log_habit",
"description": "Log that the user completed a habit today. Use when the user says they did a habit.",
"input_schema": {
"type": "object",
"properties": { "habit": { "type": "string", "description": "Habit name" } },
"required": ["habit"]
}
}
]
}The SDK
window.onekit
Everything an app can do. Feature-detect with onekit.version >= 2. Every method returns a Promise unless noted.
Core — no permission needed
onekit.ready()Resolves once the host is ready with { app, context } — context is page (visible) or background (running a chat tool).
onekit.storage.get / set / remove / allYour database: per-app JSON, persisted to disk, shared between the page and background runs. Capped at 2MB per app.
onekit.ai.complete(prompt, opts?)A Claude reply as a string. opts: { system, model: 'haiku' | 'sonnet', maxTokens }. Runs through OneKit — no key needed. Counts against the user's monthly AI allowance, so prefer haiku.
onekit.tools.register(name, handler)Register a handler for a manifest tool so Kit can call it. Register at the top level of your script.
onekit.toast(message)Show a toast in the OneKit shell.
onekit.theme.get() / onChange(cb)The active theme (white | dusk | dark). The SDK already stamps data-theme for you — use these only to react in JS.
Permission-gated
Each needs the matching permissions entry and a user grant; otherwise it rejects with err.code === "permission-denied".
onekit.notify(title, body?)System notification. notifications.
onekit.clipboard.read() / write(text)Read or write clipboard text. clipboard.read / clipboard.write.
onekit.files.pickFolder() / list / read / writeWork inside a folder the user picks — your app only ever sees an opaque handle, never a real path. utf8 text, ≤ 10MB. files.
onekit.fetch(url, opts?)HTTPS request through OneKit (no CORS). Returns { status, headers, text }. Private and loopback hosts are blocked. net.
onekit.open(url)Open an https: or mailto: URL in the user's default app. shell.open.
Always handle a denied permission gracefully:
try {
await onekit.notify("Done", "Your export finished");
} catch (err) {
if (err.code === "permission-denied") {
onekit.toast("Enable notifications for this app in Settings → Apps");
} else {
throw err;
}
}Permissions
You ask; the user grants
Declaring a permission only makes a capability requestable — it grants nothing on its own. The user approves each one, sees it in plain language, and can revoke it anytime.
notifications | “Show system notifications” |
clipboard.read | “Read your clipboard” |
clipboard.write | “Copy things to your clipboard” |
files | “Read and write files in folders you choose” |
net | “Make network requests through OneKit” |
shell.open | “Open links and files in your default apps” |
- On first open, OneKit shows a consent card with a per-permission toggle. Nothing runs until the user saves.
- Grants are stored and enforced in the OneKit main process — the sandbox is never trusted.
- Revoke anytime in Settings → Apps; the next call rejects immediately. Requesting a new permission later re-prompts.
- Request the minimum. Every permission is a reason for the user to say no.
A complete app
One file, working
State lives in onekit.storage; styling comes from the design kit. Drop this in as index.html and it runs.
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Streaks</title>
<link rel="stylesheet" href="/_onekit/theme.css" />
<script src="/_onekit/sdk.js"></script>
</head>
<body>
<div class="ok-page">
<div class="ok-title">🔥 Streaks</div>
<div class="ok-card">
<button id="hit" class="ok-btn ok-btn-primary">I did it today</button>
<p id="count" class="ok-body" style="margin-top:12px"></p>
</div>
</div>
<script>
const countEl = document.getElementById("count");
async function render() {
const days = (await onekit.storage.get("days")) || [];
countEl.textContent = days.length + " day(s) logged";
}
document.getElementById("hit").onclick = async () => {
const days = (await onekit.storage.get("days")) || [];
const today = new Date().toISOString().slice(0, 10);
if (!days.includes(today)) {
days.push(today);
await onekit.storage.set("days", days);
onekit.toast("Nice — keep the streak!");
}
render();
};
onekit.ready().then(render);
</script>
</body>
</html>Chat tools
Give Kit new abilities
Declare tools in the manifest and register handlers. Kit — OneKit's assistant — calls them like any built-in tool, with an approval card when you ask for one. Your app doesn't even need a UI.
Write each tool's description like a brief to a new assistant — say what it does and when to use it. Kit picks tools by their description.
// Top level of your script — register within ~3s of load,
// not inside ready() or an event handler.
onekit.tools.register("save_bookmark", async (input) => {
const all = (await onekit.storage.get("bookmarks")) || [];
all.push({ url: String(input.url || ""), at: Date.now() });
await onekit.storage.set("bookmarks", all);
return "Saved. The user has " + all.length + " bookmark(s).";
});Kit runs the handler in a hidden copy of your app, so keep all state in onekit.storage rather than JS variables. Set requiresConfirmation: true on anything destructive or outward-facing, and OneKit shows an approval card first. Because tools register into OneKit's tool system, they are also reachable over its local MCP server.
Share & publish
Send it to anyone
Apps live outside the OneKit bundle, so they survive updates — and they travel.
- Export any app to a
.onekitappfile from Settings → Apps. - Import one and a trust dialog shows its name, author, version, and the permissions it wants before it installs.
- Publish to the in-app App Store; others browse and install, and see an Update button when you ship a newer
version.
Start building
Get OneKit, open your apps folder in Claude Code, and describe the app you want.