Artisan commands
Generated from Command classes in api/app and api/packages
Run them inside the API container: docker compose -f api/docker-compose.yml exec api php artisan <command>. In a multi-tenant install add --domain=<tenant host> to run a command for one tenant (Tenancy). Laravel’s and third-party packages’ own commands (migrate, queue:work, passport:*, horizon, …) are not listed; php artisan list shows everything.
Package: ai
| Command | Description | Signature |
|---|---|---|
ai:usage |
AI calls, tokens and cost per day and task for this tenant | ai:usage {--days=30 : How many days back} |
Package: auth
| Command | Description | Signature |
|---|---|---|
ulams:admin |
Create a new admin account | ulams:admin |
ulams:auth:prune-agent-audit |
Delete agent audit log rows older than the retention period | ulams:auth:prune-agent-audit {--days= : Keep this many days (default: ulams_auth.agent_audit_days)} |
ulams:auth:prune-device-authorizations |
Revoke device-login tokens nobody collected and delete device requests expired for over a day | ulams:auth:prune-device-authorizations |
ulams:tokens:export-scopes |
Write the token scope vocabulary and route map as JSON for the CLI generator | ulams:tokens:export-scopes {--check : Fail when the committed JSON is out of date} |
Package: cart
| Command | Description | Signature |
|---|---|---|
cart:abandoned-event |
Find all abandoned cart in 24-48h and run event | cart:abandoned-event |
categories
Section titled “categories”Package: categories
| Command | Description | Signature |
|---|---|---|
category-permissions:seed |
Seed the database with the permissions required to support the categories | category-permissions:seed |
Package: cmi5
| Command | Description | Signature |
|---|---|---|
cmi5:move-to-bucket |
Copy cmi5 packages from another disk to the configured cmi5 disk (idempotent) | cmi5:move-to-bucket {--from=local : Disk the packages were uploaded to} |
consultations
Section titled “consultations”Package: consultations
| Command | Description | Signature |
|---|---|---|
meetings:purge-frames |
Delete stored consultation and webinar webcam frames (never the images folders) | meetings:purge-frames {--dry-run : Only list what would be deleted} |
Package: core
| Command | Description | Signature |
|---|---|---|
csp-reports:prune |
Delete aggregated CSP violation reports that were not seen for a while | csp-reports:prune {--days= : Keep reports seen within this many days (default: ulams.core.csp.retention_days)} |
course-builder
Section titled “course-builder”Package: course-builder
| Command | Description | Signature |
|---|---|---|
course-builder:eval |
Evaluate the Course Builder on golden fixtures (fake driver by default, –live for the real model) | course-builder:eval {--fixtures=coffee : all, or a comma list of coffee, injection, git, pdf, docx} {--live : call the real model} {--record : write cassettes (tests/cassettes) from this run} {--author= : user id that owns the eval sessions (default: the first admin)} {--patch=1 : also run one element-chat patch on a quiz question} {--total-minutes=30 : course length for the brief} |
course-builder:prune-events |
Delete Course Builder AG-UI events older than the retention period | course-builder:prune-events |
course-builder:session:export |
Write a builder session (brief, current and applied versions, sources and fragments) to a tar archive | course-builder:session:export {session : Session id} {path : Archive to write (.tar)} |
course-builder:session:import |
Create a builder session from an archive for an author of this tenant. The last output line is JSON with the new session id. | course-builder:session:import {path : Archive written by course-builder:session:export} {--author-email= : The author of the new session; created as an admin with an invitation when missing} {--author-name= : Display name for a new author} |
Package: demo
| Command | Description | Signature |
|---|---|---|
ulams:demo:reset |
Wipe and reseed this demo tenant (database, demo users, demo courses, cache, tokens). Requires DEMO_MODE=true. | ulams:demo:reset {--force : Do not ask for confirmation} {--wipe-files : Also delete every file on the tenant\'s default disk (bucket)} {--recapture : Take a new baseline (name, theme, accent, students) from the current database} |
ulams:demo:seed |
Seed the demo courses of this tenant and give the demo student access to every published course | ulams:demo:seed {--experience= : Demo experience to seed (coffee, oncall, nightsky, gravity, poland, ulam or all); defaults to ULAMS_DEMO_EXPERIENCE, then the tenant slug} {--skip-content : Only grant the demo student access to the published courses} |
images
Section titled “images”Package: images
| Command | Description | Signature |
|---|---|---|
images:cache-clear |
Clear images cache. | images:cache-clear |
living-course
Section titled “living-course”Package: living-course
| Command | Description | Signature |
|---|---|---|
living-course:backfill |
Record revision 1 for sources built before Living Course existed (idempotent) | living-course:backfill {--session= : Only this builder session id} |
living-course:eval |
Evaluate Living Course update proposals on golden v1/v2 fixtures (fake driver by default, –live for the real model) | living-course:eval {--fixtures=all : all, or a comma list of coffee, git, injection} {--live : call the real model for the update analysis} {--record : write cassettes (tests/cassettes) from this run} {--author= : user id that owns the eval sessions (default: the first admin)} {--max-usd=1 : total spend cap of a live run} |
living-course:poll |
Check the connected sources that are due and prune old webhook deliveries | living-course:poll |
Package: lti
| Command | Description | Signature |
|---|---|---|
ulams:lti:rotate-keys |
Rotate the tenant LTI signing keys: next becomes active, a new next key is generated, old retired keys are removed | ulams:lti:rotate-keys {--init : only create the key set when missing (tenant provisioning)} |
Package: scorm
| Command | Description | Signature |
|---|---|---|
scorm:copy-serviceworker |
Copy serviceworker.js to s3 bucket | scorm:copy-serviceworker |
ulams:scorm:publish-player |
Write the content-origin SCORM player (player.html, player.js, scorm-again) to scorm/_player on the SCORM disk | ulams:scorm:publish-player |
templates-pdf
Section titled “templates-pdf”Package: templates-pdf
| Command | Description | Signature |
|---|---|---|
templates-pdf:migrate-reportbro |
Convert stored ReportBro PDF templates and issued PDFs to pdfme (originals are backed up to storage/app/reportbro-legacy) | templates-pdf:migrate-reportbro {--dry-run : List what would change without writing} {--reset : Replace ReportBro templates with the default pdfme template of their event instead of converting them} |
tenancy
Section titled “tenancy”Package: tenancy
| Command | Description | Signature |
|---|---|---|
ulams:db:recreate-views |
Recreate the searchable_events SQL view with the current model class names | ulams:db:recreate-views |
ulams:h5p:export-config |
Write the least-privilege env files and Passport public keys the H5P service mounts in production (H5P_SERVICE_CONFIG_DIR) | ulams:h5p:export-config |
ulams:tenant:create |
Provision a tenant (database, bucket, env file, migrations, keys, demo users). Safe to re-run: finished steps are skipped. | ulams:tenant:create {slug : Lowercase letters and digits, used for hosts, database and bucket names} {--name= : Display name (APP_NAME and the global.companyName setting)} {--theme= : Front theme preset key, coffee, oncall, nightsky, gravity, poland or ulam} {--accent= : Accent colour, e.g. #C2552D} {--users=5 : Number of demo students} {--demo= : Demo mode (DEMO_MODE: login without password, hourly reset): on or off} {--db-password= : Password of a database you created yourself (TENANCY_DATABASE_PROVISIONER=manual), at least 16 characters} {--redo=* : Step to run again even if recorded as done: database, bucket, env, migrate, passport_keys, passport_client, permissions or demo} |
ulams:tenant:delete |
Delete a tenant and all its data | ulams:tenant:delete {slug} {--force : Required. Drops the database, role, bucket objects, env file and storage} |
ulams:tenant:list |
List provisioned tenants | ulams:tenant:list {--hosts : Print only the API hosts of active tenants, one per line} |
ulams:tenant:schedule-loop |
Run the scheduler of this domain every minute in one long-lived process | ulams:tenant:schedule-loop {--max-time=3600 : Exit after this many seconds (the supervisor starts it again with fresh code and config)} {--once : Run one scheduler tick now and exit} {--lock : With --once, claim the minute with the shared lock first (workers.sh lean mode: one process ticks every domain each minute)} |
ulams:tenant:seed-demo |
Create demo users and public settings for the current tenant (internal step of ulams:tenant:create) | ulams:tenant:seed-demo {--users=5 : Number of students} {--name= : Display name, stored as global.companyName} {--theme= : Front theme preset key, stored as theme.theme} {--accent= : Accent colour, stored as theme.accent} {--front-url= : Stored as global.frontURL} {--email-domain= : Domain of the demo e-mail addresses} |
ulams:tenant:set-env |
Override or reset inheritable platform settings (AI key, driver, models) for one tenant | ulams:tenant:set-env {slug} {--set=* : KEY=value override for this tenant (inheritable settings only, e.g. AI_DRIVER=fake)} {--unset=* : KEY to drop, so the tenant inherits the platform value again} |
ulams:tenant:sync-env |
Rebuild .env. |
ulams:tenant:sync-env {--migrate : Also run pending migrations for every tenant} |
ulams:tenant:work-once |
Run the scheduler tick and drain a queue once, then exit (for cron instead of long-lived workers) | ulams:tenant:work-once {--queue=default,broadcast,video : Comma-separated queues to drain, highest priority first} {--connection= : Queue connection (default: the default connection; builder and long jobs use <driver>-builder and <driver>-long-job)} {--timeout=60 : Seconds a single job may run (builder jobs run up to 1800, long jobs up to 18000)} {--max-time=50 : Stop taking new jobs after this many seconds} {--memory=256 : Memory limit of the worker in MB} {--schedule : Run one scheduler tick before the queue (only on the line that runs every minute)} |
ulams:upgrade |
Bring the platform and every tenant to the current release: migrations, permissions, keys and one-off data steps | ulams:upgrade {--tenant=* : Only these tenant slugs (the platform is then left out)} {--platform-only : Only the platform} {--force-step=* : Run this one-off step again even if it already ran} {--dry-run : List what would run; change and record nothing} |
topic-type-gift
Section titled “topic-type-gift”Package: topic-type-gift
| Command | Description | Signature |
|---|---|---|
gift:snapshot-max-scores |
Freeze the maximum score of past quiz attempts (ADR 0033) | gift:snapshot-max-scores |
topic-types
Section titled “topic-types”Package: topic-types
| Command | Description | Signature |
|---|---|---|
ulams:fill-topic-types-metadata |
Fill the metadata for the specific model | ulams:fill-topic-types-metadata {model} |
ulams:fix-topic-types-paths |
Fixes all topic types paths so they are always in courses/{id} folder | ulams:fix-topic-types-paths |
ulams:fix-type-column-name |
Fixes Topicable DBs column name from old Ulams\Courses\Models\TopicContent\XXX to Ulams\TopicTypes\Models\TopicContent\XXX |
ulams:fix-type-column-name |
translations
Section titled “translations”Package: translations
| Command | Description | Signature |
|---|---|---|
translations:merge-permissions |
Create files with translations of permissions from other packages | translations:merge-permissions |