This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/docs/PACKAGE-FORMAT.md
Jeffrey Smith 98fd3eb3e6
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-runners (push) Has been skipped
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / test-frontend (push) Successful in 6s
CI/CD / test-go-pg (push) Successful in 2m44s
CI/CD / test-sqlite (push) Successful in 2m54s
CI/CD / build-and-deploy (push) Successful in 52s
Feat v0.8.5 extension composability (#72)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
2026-04-03 11:33:47 +00:00

6.1 KiB

Package Format

Armature packages are distributed as .pkg files -- ZIP archives with a standard internal structure.

ZIP Structure

my-package.pkg
├── manifest.json        # required -- package metadata
├── js/                  # browser-side JavaScript
│   └── index.js
├── css/                 # stylesheets
│   └── styles.css
├── script.star          # Starlark entry point
├── star/                # additional Starlark modules
├── assets/              # static assets (images, etc.)
└── migrations/          # schema migration files

Only manifest.json is required. All other directories are optional and included only if present in the source.

manifest.json Reference

{
  "id": "my-package",
  "title": "My Package",
  "type": "surface",
  "tier": "browser",
  "version": "1.0.0",
  "description": "What this package does.",
  "icon": "📦",
  "author": "your-name",
  "route": "/s/my-package",
  "auth": "authenticated",
  "permissions": [],
  "api_routes": [],
  "db_tables": {},
  "settings": {},
  "hooks": {},
  "exports": [],
  "schema_version": 1
}

Required Fields

Field Description
id Unique kebab-case identifier
title Human-readable display name
type surface, extension, full, library, workflow
tier browser, starlark, sidecar
version Semver version string

Optional Fields

Field Description
description Short description
icon Emoji for sidebar/menu display
author Package author
route URL path for surfaces (e.g., /s/my-package)
auth authenticated or public
layout Surface layout mode (e.g., single)
permissions Array of required capabilities (db.write, http, notifications, secrets, realtime.publish)
api_routes Array of {"method": "GET", "path": "/items"}
db_tables Table definitions with columns and indexes
settings User-configurable settings with type, label, description, default
hooks Event bus subscription patterns
exports Functions exported for cross-package calls via lib.require()
depends Array of package IDs this package depends on
slots Named UI injection points (host surfaces declare these)
contributes Slot contributions this package injects into other surfaces
capabilities Environment requirements: {"required": [...], "optional": [...]}
schema_version Integer for additive schema migrations

Composability: Slots and Contributions

Packages compose with each other through named UI injection points (slots) and contributions.

Declaring Slots (Host Surfaces)

Surfaces declare slots where other extensions can inject UI:

{
  "slots": {
    "toolbar-actions": {
      "description": "Toolbar action buttons",
      "context": {
        "noteId": "string — current note ID",
        "getContent": "function — returns note body text"
      }
    }
  }
}

Slot names are namespaced at runtime as {package-id}:{slot-name} (e.g., notes:toolbar-actions). The context field documents what data the slot provides — it is not enforced at runtime.

Contributing to Slots

Extensions declare which slots they inject into:

{
  "contributes": {
    "notes:toolbar-actions": {
      "label": "Dictate",
      "icon": "🎤",
      "description": "Voice-to-text dictation"
    }
  }
}

Contributing extensions can be installed before or after their host surface — the coupling is soft. The admin can view all slots and their contributors at GET /api/v1/admin/slots.

Cross-Package Function Calls

Any package that declares exports can be called via lib.require(), not just library-type packages. The caller must declare the target in its depends array:

{
  "depends": ["image-gen"],
  "permissions": ["api.http"]
}

Package Lifecycle

  1. Install: Upload a .pkg file via Admin > Packages or POST /api/v1/admin/packages/install. The kernel extracts the archive, creates database tables, and registers routes.

  2. Enable: Activate the package so its routes, hooks, and UI become available. PUT /api/v1/admin/packages/:id/enable.

  3. Disable: Deactivate without removing data. PUT /api/v1/admin/packages/:id/disable.

  4. Update: Upload a new .pkg with a higher semver version. Schema changes must be additive (new columns/tables only). POST /api/v1/admin/packages/:id/update.

  5. Export: Download the installed package as a .pkg archive. GET /api/v1/admin/packages/:id/export.

  6. Delete: Remove the package and its data. DELETE /api/v1/admin/packages/:id.

Bundled vs User-Installed

Bundled packages ship inside the Docker image at /app/bundled-packages. On first boot, a curated default set is auto-installed. Behavior:

  • First boot: curated defaults are installed and enabled.
  • Subsequent boots: already-installed packages are skipped.
  • Admin uninstalls: the package stays uninstalled (never force-reinstalled).
  • Control via BUNDLED_PACKAGES env var: empty = defaults, "*" = all, or comma-separated IDs.

User-installed packages are uploaded through the Admin UI or API. They follow the same lifecycle but are not tied to the Docker image.

Building Packages

Packages are built by zipping the standard directories alongside manifest.json:

# Build all packages in the packages/ directory
cd packages && bash build.sh

# Build a single package
cd packages && bash build.sh my-package

Output goes to dist/my-package.pkg.

Manual Build

cd packages/my-package
zip -r ../../dist/my-package.pkg manifest.json js/ css/ script.star

The build script automatically includes whichever standard directories exist: js/, css/, assets/, script.star, star/, migrations/.

Custom Docker Image

To bundle custom packages into a Docker image:

  1. Place your package in packages/your-package/ with a manifest.json.
  2. Run cd packages && bash build.sh all.
  3. Build the image: docker build -t my-armature .

The Dockerfile builds all packages and copies them into the bundled packages directory.