All checks were successful
CI/CD / detect-changes (pull_request) Successful in 5s
CI/CD / test-runners (pull_request) Has been skipped
CI/CD / e2e-smoke (pull_request) Has been skipped
CI/CD / test-frontend (pull_request) Successful in 6s
CI/CD / test-go-pg (pull_request) Successful in 2m42s
CI/CD / test-sqlite (pull_request) Successful in 2m58s
CI/CD / build-and-deploy (pull_request) Successful in 1m33s
Manifest declarations for slots (host surfaces declare injection points) and contributes (extensions declare UI contributions). lib.require() relaxed from library-only to any package with exports. Admin slots aggregation endpoint. SDK renderAll() helper. 7 new tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
188 lines
6.1 KiB
Markdown
188 lines
6.1 KiB
Markdown
# 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
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```json
|
|
{
|
|
"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`:
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
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.
|