Building Apps for AnisminOS 🏗️

Classification: Public | Last updated: 2026-03-05

Quick Start

Every app lives in apps/<app-name>/ with three core files:

apps/my-app/
├── app.json         # Manifest
├── routes.py        # Flask Blueprint
└── templates/
    └── my-app.html  # UI template

App Manifest (app.json)

{
  "id": "my-app",
  "name": "My App",
  "icon": "🎯",
  "description": "What my app does.",
  "path": "/my-app",
  "window": { "width": 900, "height": 650, "noPadding": true },
  "settings": [
    { "key": "refresh_interval", "label": "Refresh interval", "type": "select", "options": ["5s", "10s", "30s"], "default": "10s" },
    { "key": "compact_view", "label": "Compact view", "type": "toggle", "default": false }
  ]
}

Setting types: toggle, slider, select, text, color


Routes (routes.py)

from auth import check_permission
from flask import Blueprint, render_template, request, jsonify

bp = Blueprint('my_app', __name__, template_folder='templates')

@bp.route('/')
def index():
    return render_template('my-app.html')

@bp.route('/api/data')
@check_permission('my-app', 'read')
def get_data():
    return jsonify({'items': []})

@bp.route('/api/data', methods=['POST'])
@check_permission('my-app', 'write')
def create_item():
    body = request.get_json(silent=True) or {}
    return jsonify({'ok': True})

The HTML shell (/) skips permission checks — permissions are enforced on API routes.


Template Skeleton

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My App</title>
    <link rel="stylesheet" href="/api/design.css">
    <script src="/api/frame.js"></script>
</head>
<body>
    <div class="app-shell">
        <aside class="sidebar">
            <div class="sidebar-header">
                <div class="sidebar-title"><span>🎯</span> My App</div>
            </div>
            <nav class="sidebar-nav">
                <div class="nav-item active">📋 Main</div>
            </nav>
        </aside>
        <main class="main-content" id="content"></main>
    </div>
    <script>
        fetch('/my-app/api/data').then(r => r.json()).then(renderData);
    </script>
</body>
</html>

Design System

Import /api/design.css for the full component library.

Key CSS Variables

var(--os-accent)         /* #29b6f6 */
var(--os-text)           /* #e3f2fd */
var(--os-text-secondary) /* #90caf9 */
var(--os-surface)        /* dark translucent */
var(--os-border)         /* border color */
var(--os-radius)         /* 8px */
var(--os-blur)           /* 10px backdrop blur */
var(--os-transition)     /* 0.2s ease */

Layout Classes

.app-shell, .sidebar, .sidebar-header, .sidebar-nav, .nav-item, .main-content

Component Classes

.card, .btn, .btn-primary, .btn-danger, .badge, .badge-success, .badge-warning, .badge-danger, .empty-state, .loading


Frame SDK (Settings)

const val = await AnisminOS.settings.get('my-app', 'refresh_interval');
await AnisminOS.settings.set('my-app', 'compact_view', true);
AnisminOS.settings.onChange('my-app', 'compact_view', (v) => { ... });

Security Checklist

Before submitting for review:

  • [ ] Data-mutating routes use @check_permission('my-app', 'write')
  • [ ] Data-reading routes use @check_permission('my-app', 'read')
  • [ ] SQL uses parameterized placeholders only
  • [ ] DB connections returned in finally blocks
  • [ ] No hardcoded secrets
  • [ ] User input validated (type, length, allowlist)
  • [ ] No shell=True with user data
  • [ ] File paths canonicalized with os.path.realpath
  • [ ] No secrets in logs

See Developer Security Guide for full details.


Auto-Discovery

Just create the directory + app.json and restart. The server discovers and registers automatically.