OneKit

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.

1

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.

2

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.

3

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
~/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:

index.html — in <head>
<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.

nameRequired. String, ≤ 60 chars.
iconOptional emoji, ≤ 8 chars.
descriptionOptional, ≤ 200 chars.
entryRelative .html path in the folder. Default index.html.
backgroundPage loaded headlessly to run chat tools. Default = entry.
versionStrict semver x.y.z. Default 0.0.1 — bump it to ship updates.
authorOptional, ≤ 60 chars. Shown when sharing or publishing.
permissionsArray, max 8, from the six values below.
toolsArray, max 10. Chat tools Kit can call (see below).
onekit.json
{
  "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 / all

Your 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 / write

Work 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:

permission-denied
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.

notificationsShow system notifications
clipboard.readRead your clipboard
clipboard.writeCopy things to your clipboard
filesRead and write files in folders you choose
netMake network requests through OneKit
shell.openOpen 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.

index.html
<!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.

index.html — script
// 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 .onekitapp file 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.