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
finallyblocks - [ ] No hardcoded secrets
- [ ] User input validated (type, length, allowlist)
- [ ] No
shell=Truewith 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.