# Building for ManAurum OS — AI Agent Instructions

## What this platform is

ManAurum OS is a **multi-tenant** browser-based virtual desktop. Third-party apps run in iframes inside per-tenant workspaces. Communication between the shell and your app happens via postMessage. The platform runs on Docker (Dokploy + Traefik) with hosted apps served via FastAPI StaticFiles. Every app is scoped to a tenant; cross-tenant data access is structurally impossible (Postgres RLS).

## Ways to create apps

There are two paths today. **The Deploy API path is canonical for external developers.**

| Surface | How it works | Who uses it |
|---------|-------------|-------------|
| **Deploy API (canonical)** | Manifest v1 + bundle ZIP → `POST /api/dev/apps/deploy` with `mnu_*` token → versioned app in tenant catalog | External developers, CI/CD, AI agents |
| **App Builder UI (legacy)** | Chat-based AI generation OR paste HTML / upload ZIP via Dev Hub with `mdev_*` tokens | Premium users + workspace-scoped flows |

Both surfaces produce the same canonical app (AppListing + AppVersion). Lifecycle: Draft → Private → Unlisted → Public.

> **One token = one tenant.** A `mnu_*` token issued from one tenant's Developer Console deploys only into that tenant. Deploying the same app to another tenant requires a separate token issued from THAT tenant.

## Canonical flow (Deploy API)

1. Sign in as a tenant developer → Developer Console → Tokens → "Issue new token". Save as `MANAURUM_TENANT_TOKEN=mnu_<env>_<32>`.
2. Author a manifest validated against `/sdk/manifest_v1.schema.json` (NOT the legacy `manifest.schema.json`).
3. Bundle: `zip -r bundle.zip .` (your `entry_point` HTML at the root, max 50 MB).
4. Deploy:

```bash
B64=$(base64 -w0 bundle.zip)
jq -n --arg b "$B64" --slurpfile m manifest.json '{manifest: $m[0], bundle: $b}' \
  | curl -X POST https://manaurum.com/api/dev/apps/deploy \
      -H "Authorization: Bearer $MANAURUM_TENANT_TOKEN" \
      -H "Content-Type: application/json" \
      -d @-
```

Response: `{ application_id, version_id, version_number, deployed_at, url }`. Workspace owner inside the same tenant installs the app via AppStore; users open it at `/t/<tenant_slug>/apps/<app_slug>`.

## Tenant context in the iframe

On `manaurum:init`, the payload includes:

```json
{
  "tenant":     { "id": "<uuid>", "slug": "<slug>" },
  "workspace":  { "id": "<uuid>" },
  "user":       { "id": "<id>", "nickname": "<id>" },
  "app":        { "slug": "<slug>", "version_id": "<uuid>" },
  "permissions": [...],
  "windowId":    "<app_slug>"
}
```

Use `payload.tenant.slug` for B2B kustomization (logo, accent colour, per-tenant config). Do NOT use it as a security filter — RLS already isolates data per tenant.

## Runtime data API (`manaurum.db.*`)

If your manifest declares `entities[]` and requests `db.read_own_entities` / `db.write_own_entities`, the SDK exposes typed CRUD over `app_records` (RLS-scoped to your tenant):

- `app.db.create(type, data)` / `app.db.get(type, id)` / `app.db.list(type, { sort_by?, sort_order?, limit?, offset?, where? })` / `app.db.update(type, id, patch)` / `app.db.delete(type, id)`

Only fields declared with `indexed: true` are sortable/filterable. See `manaurum.js` and the manaurum-dev-sdk skill for the full contract.

---

## Legacy App Builder UI (workspace-scoped paste-HTML)

The sections below describe the **legacy** in-platform App Builder / Dev Hub flow with `mdev_*` workspace tokens. New external developers should use the canonical Deploy API path above.

## Quickest path to a working app

1. Create an HTML file with `<script src="https://manaurum.com/sdk/manaurum.js"></script>`
2. Call `ManaurumSDK.init()` and handle `onReady`
3. Register the app in Dev Hub (one-click "Create App" → enter name)
4. Paste your HTML in the Build tab, or set an external HTTPS URL as entrypoint
5. Click "Publish" — app appears on your desktop immediately

**Hosting options:**
- **Internal hosting** — paste HTML or upload ZIP → served at `https://manaurum.com/api/hosted/{slug}/index.html`
- **External hosting** — host on any HTTPS server (Vercel, Netlify, GitHub Pages) and set entrypoint URL

## Machine-readable resources

| Resource | URL | Purpose |
|----------|-----|---------|
| **Manifest v1 JSON Schema (canonical)** | `/sdk/manifest_v1.schema.json` | Validate Deploy API manifests against the v1 contract |
| Manifest JSON Schema (legacy) | `/sdk/manifest.schema.json` | Legacy App Builder UI manifest validation |
| Permission registry | `/sdk/permissions.json` | All valid permission IDs (legacy) |
| Runtime events | `/sdk/events.json` | All postMessage event types with payloads |
| Review checklist | `/sdk/review-checklist.json` | Pre-submission requirements |
| Manifest template | `/sdk/templates/manifest.json` | Valid starter manifest |
| SDK (UMD) | `/sdk/manaurum.js` | Include via script tag |
| SDK (ESM) | `/sdk/manaurum.mjs` | Import as ES module |
| Hello world example | `/sdk/example.html` | Working minimal app |
| Test harness | `/sdk/test-harness.html` | Test your app locally |

## Manifest

Your app is defined by a JSON manifest. Validate against `/sdk/manifest.schema.json`.

Required fields:
- `manifest_version`: always `1`
- `app_id`: lowercase alphanumeric + hyphens, 3-50 chars, globally unique
- `name`: 1-100 chars
- `version`: semver format X.Y.Z
- `runtime.type`: always `"iframe"`
- `runtime.entrypoint`: HTTPS URL of your app (or `https://manaurum.com/api/hosted/{slug}/index.html` for internally hosted)
- `window.default_size`: `{ width: 300-2000, height: 200-1500 }`

Optional but recommended:
- `window.min_size`: `{ width: 200-2000, height: 150-1500 }` — minimum window dimensions. Users can resize windows by dragging edges/corners but cannot go below this. If omitted, defaults to 200×150. The shell also enforces this when apps call `manaurum:resize`.
- `description.short`: 1-160 chars (required for App Store listing)
- `icon`: HTTPS URL to 256x256 PNG (or emoji for simple apps)
- `category`: one of `productivity`, `utility`, `lifestyle`, `entertainment`, `dev_tools`, `other`
- `permissions`: array of permission IDs from `/sdk/permissions.json`

## Runtime integration

### Minimal implementation

```html
<script src="https://manaurum.com/sdk/manaurum.js"></script>
<script>
  var app = ManaurumSDK.init();
  app.onReady(function(ctx) {
    // ctx.theme = 'smoothie' | 'xp'
    // ctx.user.nickname = 'User'
    // ctx.permissions = ['theme.read', ...]
    // ctx.device = 'desktop' | 'mobile'
    console.log('Connected to ManAurum OS');
  });
  app.onThemeChange(function(theme) {
    // Adapt UI to new theme
  });
</script>
```

### Without SDK (raw postMessage)

```javascript
window.addEventListener('message', function(event) {
  if (event.data.type === 'manaurum:init') {
    var ctx = event.data.payload;
    // Use ctx.theme, ctx.user, ctx.permissions
    event.source.postMessage({ type: 'manaurum:ready', payload: {} }, event.origin);
  }
});
```

### Available app → shell messages

| Event | Payload | Permission |
|-------|---------|------------|
| `manaurum:ready` | `{}` | none (required) |
| `manaurum:set-title` | `{ title }` | `window.manage` |
| `manaurum:resize` | `{ width, height }` | `window.manage` (clamped to min_size) |
| `manaurum:close` | `{}` | `window.manage` |
| `manaurum:toast` | `{ type: 'success'|'error'|'info', message }` | `toast.send` |
| `manaurum:active-record` | `{ entity_type, record_id, record_title? }` | none |

### Telling the OS Assistant what the user is looking at

Send `manaurum:active-record` whenever the user opens (or switches
to) a specific record in your app — a task, an order, a document.
The OS Assistant attaches it to the user's next message so
"complete this" / "delete this" resolve to that record without the
user re-typing what "this" is. Send it again with a different
`record_id` when the selection changes; send `{ record_id: '' }`
(or any empty id) when the user closes the record. The shell also
clears it automatically when your window closes. It's a local
selection hint — no data leaves your app, so it needs no
permission. `entity_type` is a coarse label you choose (`'task'`,
`'order'`, …); `record_id` is whatever id your agent tools accept.

## Window resize behavior

Users can resize any window by dragging its edges (4 edges + 4 corners). The OS enforces `window.min_size` from the manifest — windows cannot be resized below this. New windows open centered on screen.

Apps can also resize programmatically via `manaurum:resize` (requires `window.manage` permission). The shell clamps requested dimensions to `min_size`. If no `min_size` is declared in the manifest, the default minimum is 200×150 pixels.

**Important for app developers:** Always declare `window.min_size` in your manifest to prevent your layout from breaking when users resize the window. Use responsive/flexible layouts that adapt to any size between `min_size` and the full screen.

## Permissions

9 valid permissions:
- `user.profile.read` — user's nickname
- `theme.read` — current OS theme
- `window.manage` — resize, title, close window
- `toast.send` — show toast notifications
- `notifications.send` — send persistent notifications
- `notifications.schedule` — schedule reminders
- `tasks.suggest` — suggest tasks to Work Assistant
- `storage.read` — read stored data (server-side, per user)
- `storage.write` — save and delete stored data (5MB per app)

Do NOT invent other permission strings. They will be rejected.

## Security policy

All hosted apps receive a Content-Security-Policy meta tag injected at deployment time:

| App type | CSP connect-src | Effect |
|----------|----------------|--------|
| AI-generated (`build_source_type: ai_generated`) | `'none'` | No fetch/XHR/WebSocket |
| Standard developer (paste/upload) | `'self'` | Same-origin requests only |
| Trusted developer | `'self' https:` | Any HTTPS requests |

All hosted apps also get: `frame-src 'none'; object-src 'none'` (no nested iframes, no plugins).

The iframe sandbox is always: `allow-scripts allow-forms allow-same-origin`.

## Publishing requirements

Before publishing, ensure:
- [ ] HTTPS entrypoint responds HTTP 200 (localhost OK for private apps)
- [ ] All declared permissions are from the allowed list
- [ ] Version is valid semver (X.Y.Z)
- [ ] Window size within allowed range (300-2000 width, 200-1500 height)

For public App Store listing (requires admin review):
- [ ] App icon uploaded (PNG, 256x256)
- [ ] Short description filled (1-160 chars)
- [ ] At least 1 screenshot uploaded
- [ ] App responds to `manaurum:init` with `manaurum:ready` within 10 seconds

## Release lifecycle

All apps follow the same lifecycle regardless of how they were created:

```
Draft → Published/Private → Published/Unlisted → Published/Public
```

| Visibility | Who can see | Review needed | Share token |
|------------|------------|---------------|-------------|
| Private | Only the creator | No | No |
| Unlisted | Anyone with the link | No | Auto-generated |
| Public | Everyone (App Store) | Yes (admin review) | N/A |

**Version updates:** When a new version is published with the same permissions, installed copies update silently. If new permissions are added, users see a confirmation dialog before updating.

## App Builder (AI-generated apps)

App Builder creates apps through the same unified authoring contract:

1. `create_app_draft()` — creates canonical AppListing + draft AppVersion immediately
2. AI generates HTML artifact → stored in `AIAppProject.current_html` (working buffer)
3. On publish: `commit_working_buffer()` → `attach_artifact()` → `publish_version()`
4. The app appears on the user's desktop as a normal iframe app

AI-generated apps:
- Use `creation_source: "ai_builder"` on AppListing
- Use `build_source_type: "ai_generated"` on AppVersion
- Get the most restrictive CSP (`connect-src 'none'`)
- Are owned by the real user's Developer identity (not a fake "AI Builder" account)
- Can be promoted from Private → Unlisted → Public like any other app
- Can be edited in Dev Hub for manual refinement

## AI Assistant Integration (Optional)

Your app can register capabilities with the OS-level AI Assistant. When users type natural language into the Assistant, it can route actions to your app.

### Add agent section to manifest

```json
{
  "agent": {
    "enabled": true,
    "capabilities": [
      {
        "name": "create_todo",
        "description": "Create a to-do item",
        "input_schema": {
          "title": { "type": "string", "required": true, "description": "Task title" },
          "due_date": { "type": "date", "required": false, "description": "Due date YYYY-MM-DD" }
        },
        "routing_hints": ["todo", "task", "remind", "need to"],
        "trust_default": "suggest",
        "example": { "title": "Buy groceries", "due_date": "2026-04-15" }
      }
    ],
    "emitted_events": ["todo.created"],
    "subscribed_events": []
  }
}
```

### How it works

1. User types in Assistant: "I need to buy groceries tomorrow"
2. AI matches `routing_hints` + `description` → selects your capability
3. AI fills `input_schema` fields from the message
4. User confirms → OS sends `manaurum:agent-preview` to your app
5. Your app executes → responds with `manaurum:agent-result`

### Handle agent messages

```javascript
app.on('agent-preview', function(data) {
  // data.request_id — correlation ID
  // data.capability — "create_todo"
  // data.fields — { title: "Buy groceries", due_date: "2026-04-15" }

  // Execute the action in your app
  var result = createTodo(data.fields);

  // Report back
  app.send('agent-result', {
    request_id: data.request_id,
    success: true,
    record_id: result.id
  });
});
```

### Capability guidelines

- `name`: unique within your app, lowercase with underscores
- `description`: clear, used by AI to decide routing — be specific
- `routing_hints`: keywords that suggest this action — the more, the better matching
- `trust_default`: `suggest` (user confirms), `auto_save` (auto for high confidence)
- `input_schema`: fields your action accepts — same format as manifest window/permissions

## Common mistakes

1. Using HTTP instead of HTTPS for entrypoint
2. Forgetting to send `manaurum:ready` after receiving `manaurum:init`
3. Declaring permissions that don't exist (9 valid: user.profile.read, theme.read, window.manage, toast.send, notifications.send, notifications.schedule, tasks.suggest, storage.read, storage.write)
4. Window default_size outside allowed range (300-2000 width, 200-1500 height)
5. Using `manifest_version: 2` (only version 1 exists)
6. App slug with uppercase letters or spaces
7. Using `/hosted/` instead of `/api/hosted/` for internally hosted apps
8. Using `localStorage` instead of ManAurum Storage API (`storage.read`/`storage.write` permissions)

## Developer journey

### Via Dev Hub (manual)
1. Open Dev Hub → enter Display Name → "Register as Developer"
2. Click "Create App" → enter app name (slug auto-generated)
3. Build tab → paste entrypoint URL or upload HTML/ZIP
4. Click "Publish" → app opens on desktop, auto-installs
5. To release updates: Versions tab → "Release Update" → enter version + notes → "Publish"
6. All installed copies update automatically — no reinstall needed

### Via App Builder (AI)
1. Open App Builder (premium) → describe what you want
2. AI generates the app → preview in real-time
3. Edit with follow-up prompts → iterate
4. Click "Publish" → app appears on desktop
5. Re-edit anytime → new version auto-published

### Via Skill Manaurum (Assistant)
1. Open OS Assistant → say "Create me a todo app"
2. AI generates app → shows preview in Assistant Panel
3. Click "Open in App Builder" → review and publish

All paths create the same canonical app. An app started via AI can be opened in Dev Hub for manual refinement, and vice versa.
