Repository navigation
API Tooling
Caligrafy Quill 2.26.0. Generated from the source by
php qa/build-api-reference.php; the guides explain how to use these classes, this reference says exactly what each public method takes and returns. Back to the API reference.
The framework bootstrap and what the caligrafer command runs: the CLI, doctor, update, upgrade, deploy, Cloud, quickstart and the plugin copy.
| Class | What it is |
|---|---|
Caligrafy\Features |
The SDKs that only some applications use are no longer in Caligrafy's composer.json (they are suggested): a project adds the one it needs with php caligrafer.php add <feature>, which runs composer require with the ... |
Caligrafy\FeatureMissing |
A feature whose SDK (or files) this project does not install: the message names the command that adds it |
Caligrafy\Framework |
Class Framework that loads the entire framework Framework loader |
Caligrafy\Loader |
Class Loader loads any additional librairies and helpers needed per module Framework loader loads any additional librairies and helpers needed per module |
Caligrafy\ClassMap |
Before 2.18.0 the first unknown class of a request loaded every PHP file of framework/core, framework/database, framework/interfaces, application/ and tests/. |
Caligrafy\Caligrafer |
Class Caligrafer: the framework as the command line starts it (php caligrafer.php): settings, constants and the class map, without routes or a session cookie |
Caligrafy\Doctor |
Every check is read-only: it lists, reads or connects, it never creates, charges or sends anything (the only exception is Watson when its assistants cannot be listed: a session is opened and closed right away). |
Caligrafy\Updater |
php caligrafer.php update [<version>] downloads the project's release (Framework::VERSION) and the target release (the latest by default), then: 1. framework/ and claude-plugin/ are the framework's: replaced whole ... |
Caligrafy\Upgrader |
Compares application/ with framework/settings/application and reports or applies the template updates. |
Caligrafy\Deployer |
1. waits for the database (a new server starts its database at the same time as the app) 2. creates the framework tables (framework/settings/platform.sql, safe to run again) 3. runs every ... |
Caligrafy\CloudClient |
Calls the capabilities of Caligrafy Cloud (POST <url>/capabilities/<name>) with a personal access token of the account. |
Caligrafy\Quickstart |
Every step can be run again: it only does what is missing. |
Caligrafy\PluginSource |
A project enables the Claude Code plugin of its own claude-plugin/ folder through .claude/settings.json: a plugin source (a "marketplace", declared in .claude-plugin/marketplace.json) at "./", and the plugin ... |
namespace Caligrafy;
class FeaturesSource: framework/core/Features.php:30
Class Features (2.26.0) The SDKs that only some applications use are no longer in Caligrafy's composer.json (they are suggested): a project adds the one it needs with php caligrafer.php add <feature>, which runs composer require with the pinned constraint. The framework classes that use an SDK call Features::need() first: without it they throw FeatureMissing, whose message names the command, never a fatal "class not found".
FaceDetect's models (@vladmandic/face-api 1.7.15) and the pictures of its recognition example are not in the release either: php caligrafer.php add facedetect (and php caligrafer.php facedetect <app>) download them from pinned, versioned sources and check the SHA-256 of each file before anything is written.
used() tells which features a project uses (its .env settings and its application/ code): doctor reports the ones whose SDK is missing, update keeps (or adds back) their packages in composer.json.
SINCE = '2.26.0'FEATURES = ...CLASSES = ...PLACEHOLDERS = array('pk_test_1234', 'sk_test_1234', 'pk_live_1234', 'sk_live_1234')FACE_API = '1.7.15'MODELS_DIR = 'public/resources/models'MODELS = ...MODEL_SOURCES = array('https://cdn.jsdelivr.net/npm/@vladmandic/face-api@1.7.15/model/', 'https://unpkg.com/@vladmandic/face-api@1.7.15/model/',)PICTURES_DIR = 'recognition/Flash'PICTURES = ...PICTURE_SOURCES = ...
-
public static $absent = array(): array tests: packages treated as not installed -
public static $fetcher = null: callable|null tests: fn (string $url): ?string, the body of a download (null when it failed)
Features::names()Features::installedPackage()Features::missing()Features::installed()Features::need()Features::message()Features::requireCommand()Features::used()Features::toRestore()Features::add()Features::facedetectReady()Features::modelFiles()Features::pictureFiles()Features::download()
public static function names()The feature names: payments, ai, push, passkeys, snap (SDKs) and facedetect (files)
Source: framework/core/Features.php:141
public static function installedPackage($package)Whether a package is installed (its class loads)
Parameters
$package
Source: framework/core/Features.php:149
public static function missing($feature)The packages of a feature that are not installed: [name => constraint]
Parameters
$feature
Source: framework/core/Features.php:159
public static function installed($feature)No doc comment.
Parameters
$feature
Source: framework/core/Features.php:165
public static function need($feature, $what)Throws FeatureMissing when the feature's SDK is not installed
Parameters
$feature-
$whatstring: what needs it (Caligrafy\Payment, new Agent() with Groq...)
Source: framework/core/Features.php:174
public static function message($feature, $what = null)The message of a missing SDK: what needs it, the packages, the command that installs them
Parameters
$feature-
$what, defaultnull
Source: framework/core/Features.php:183
public static function requireCommand(array $features)The composer require command of features: composer require --no-interaction 'a/b:^1' ...
Parameters
-
$featuresarray
Source: framework/core/Features.php:193
public static function used($root = '.', $env = null)The features a project uses: [feature => [reasons]], from its .env settings (set, not a placeholder) and its application/ code. Values of .env are never returned, only the names of the settings
Parameters
-
$root, default'.' -
$envcallable|null, defaultnull: fn(string $name): string, a setting (default: the project's .env file)
Source: framework/core/Features.php:209
public static function toRestore($root, array $composer, $env = null)The require entries a project's composer.json lacks for the features it uses: [package => [constraint, feature, reason]]. Packages it already requires (any constraint) are left alone
Parameters
$root-
$composerarray -
$env, defaultnull
Source: framework/core/Features.php:242
public static function add(array $features, $root = '.', $run = null, $out = null)php caligrafer.php add <feature>...: composer require for the SDKs, the downloads for facedetect
Parameters
-
$featuresarray -
$root, default'.' -
$runcallable|null, defaultnull: fn(string $command): int (tests fake Composer) -
$outcallable|null, defaultnull: fn(string $line)
Returns int: exit code
Source: framework/core/Features.php:260
public static function facedetectReady($root = '.')Whether the FaceDetect models are in place (each file there with its size)
Parameters
-
$root, default'.'
Source: framework/core/Features.php:307
public static function modelFiles($root = '.')The model files: [path, sha256, size, urls]
Parameters
-
$root, default'.'
Source: framework/core/Features.php:318
public static function pictureFiles($appDir)The pictures of the recognition example of a FaceDetect app (its folder)
Parameters
$appDir
Source: framework/core/Features.php:326
public static function download(array $files)Downloads the files that are missing, checks the SHA-256 of each, then writes them all; nothing is written when one fails. A file already there with another content is left alone (yours)
Parameters
-
$filesarray
Returns array: lines of the report
- @throws \RuntimeException offline, or a download that does not match its SHA-256
Source: framework/core/Features.php:337
namespace Caligrafy;
class FeatureMissing extends \LogicExceptionSource: framework/core/Features.php:14
A feature whose SDK (or files) this project does not install: the message names the command that adds it
No public methods.
namespace Caligrafy;
class Framework extends \stdClassSource: framework/core/Framework.php:19
Class Framework that loads the entire framework Framework loader
-
VERSION = '2.26.0': Version of Caligrafy Quill (semantic versioning: the git tag is v<VERSION>, the changes are in CHANGELOG.md)
public static function run()Framework main
Source: framework/core/Framework.php:29
public static function mappedFolders()The folders of the class map: the framework's classes and the application's
Source: framework/core/Framework.php:78
public static function unmappedFolders()Left out of the class map: settings and routes (loaded by dispatch), views (Pug)
Source: framework/core/Framework.php:86
namespace Caligrafy;
class Loader extends \stdClassSource: framework/core/Loader.php:20
Class Loader loads any additional librairies and helpers needed per module Framework loader loads any additional librairies and helpers needed per module
public function library($lib)No doc comment.
Parameters
$lib
Source: framework/core/Loader.php:23
public function helper($helper)No doc comment.
Parameters
$helper
Source: framework/core/Loader.php:28
namespace Caligrafy;
class ClassMapSource: framework/core/ClassMap.php:24
Class ClassMap (2.18.0) Before 2.18.0 the first unknown class of a request loaded every PHP file of framework/core, framework/database, framework/interfaces, application/ and tests/. Now a map of class => file (namespaced or not, interfaces, traits and enums too) is built once from those folders (application/config, application/views and tests/ left out) and kept in a private folder of the temporary directory; a class is loaded when it is first used.
Kept as before: a file that does more than declare classes (it declares functions, or runs code at the top level, or declares a class inside an if) is loaded at start, as every file used to be. A class missing from the map, and the list of capabilities, make the map check its files (once per request; with APP_ENV=production at most once a minute): a file added, removed or changed since the map was built rebuilds it, so a new class works at once while you develop. A new container starts with no map. CLASS_MAP=false in .env goes back to loading every file.
VERSION = 2PRODUCTION_CHECK = 60
ClassMap::enabled()ClassMap::register()ClassMap::load()ClassMap::capabilityFiles()ClassMap::refresh()ClassMap::registered()ClassMap::map()ClassMap::rebuild()ClassMap::declarations()ClassMap::tempFolder()
public static function enabled()Whether the class map is on (CLASS_MAP=false turns it off)
Source: framework/core/ClassMap.php:38
public static function register(array $dirs, array $skip = array())Registers the autoloader for these folders and loads the files that must run at start
Parameters
-
$dirsarray: absolute folders to map -
$skiparray, defaultarray(): absolute folders inside them to leave out
Source: framework/core/ClassMap.php:48
public static function load($class)The autoloader
Parameters
$class
Source: framework/core/ClassMap.php:78
public static function capabilityFiles($fresh = false)The files that may declare capabilities (they mention #[Capability]): Capabilities loads them to find every one
Parameters
-
$fresh, defaultfalse
Source: framework/core/ClassMap.php:93
public static function refresh()Rebuilds the map when a file was added, removed or edited since it was built (checked once per request)
Source: framework/core/ClassMap.php:107
public static function registered()Whether the autoloader is registered
Source: framework/core/ClassMap.php:133
public static function map()The map, from memory, the cached file or a new scan
Source: framework/core/ClassMap.php:141
public static function rebuild()Scans the folders again and saves the map
Source: framework/core/ClassMap.php:158
public static function declarations($source)What a PHP file declares at its top level: its classes (with their namespace), and whether it does anything else (functions, code, a class declared inside a block), which makes it a file to load at start
Parameters
$source
Returns array: ['classes' => [...], 'eager' => bool]
Source: framework/core/ClassMap.php:250
public static function tempFolder($kind)A private folder of the system's temporary directory for this project, this system user and one kind of cache (views, classes, data): created 0700, null when it cannot be created or written, is a link, or someone else owns it
Parameters
$kind
Source: framework/core/ClassMap.php:339
namespace Caligrafy;
class Caligrafer extends \stdClassSource: framework/core/Caligrafer.php:20
Class Caligrafer: the framework as the command line starts it (php caligrafer.php): settings, constants and the class map, without routes or a session cookie
public static function run()Caligrafer main
Source: framework/core/Caligrafer.php:25
public static function generateKeys()Generates Keys from the command line
Source: framework/core/Caligrafer.php:35
public static function generateApiKey()Generates Api Key from the command line
Source: framework/core/Caligrafer.php:49
public static function dispatch()Dispatches the URL to the route
Source: framework/core/Caligrafer.php:100
namespace Caligrafy;
class DoctorSource: framework/core/Doctor.php:22
Class Doctor Every check is read-only: it lists, reads or connects, it never creates, charges or sends anything (the only exception is Watson when its assistants cannot be listed: a session is opened and closed right away). Secret values are never printed: at most a known prefix and the last 4 characters.
Base URLs can be overridden (tests, proxies, regional endpoints): OPENAI_API_BASE, GROQ_API_BASE, AGENT_API_BASE, STRIPE_API_BASE, COINBASE_API_BASE. Watson always uses WATSON_URL.
CHECKS =['project', 'database', 'openai', 'groq', 'agent', 'stripe', 'coinbase', 'watson', 'mail']LABELS = ...TIMEOUT = 8PLACEHOLDERS =['your-smtp-host', 'pk_test_1234', 'sk_test_1234', 'pk_live_1234', 'sk_live_1234']PREFIXES =['sk-proj-', 'sk-svcacct-', 'sk-admin-', 'gsk_', 'sk_test_', 'sk_live_', 'rk_test_', 'rk_live_', 'pk_test_', 'pk_live_']
->__construct()->run()->results()Doctor::formsWithoutMethod()Doctor::pugPitfalls()Doctor::mask()
public function __construct($root = '.', $only = null, $envFile = '.env')No doc comment.
Parameters
-
$root, default'.' -
$only, defaultnull -
$envFile, default'.env'
Source: framework/core/Doctor.php:40
public function run($json = false)Runs the checks and prints the report
Parameters
-
$json, defaultfalse
Returns int: exit code: 0 without FAIL, 1 otherwise
Source: framework/core/Doctor.php:51
public function results()No doc comment.
Source: framework/core/Doctor.php:85
public static function formsWithoutMethod($root)The forms of application/views (Pug) with an email or a password field and no method: "file:line" each
Parameters
$root
Source: framework/core/Doctor.php:791
public static function pugPitfalls($root)Pug mistakes of application/views that only show when the page renders: "file:line $digits" for a dollar sign before digits (outside comments), "file:line - if" for code on the same line as a one-line - if / - foreach
Parameters
$root
Source: framework/core/Doctor.php:822
public static function mask($value)No doc comment.
Parameters
$value
Source: framework/core/Doctor.php:849
namespace Caligrafy;
class UpdaterSource: framework/core/Updater.php:26
Class Updater php caligrafer.php update [<version>] downloads the project's release (Framework::VERSION) and the target release (the latest by default), then:
- framework/ and claude-plugin/ are the framework's: replaced whole (files the release dropped go too), except the files initialize writes there (KEEP). Framework files changed by hand are named in the report
- every other file of the release (caligrafer.php, .bin/, Dockerfile, index.php, public/css, public/js...) is compared with the project's release: an untouched copy is replaced, a missing one added, a changed one kept with the new version written next to it (<file>.caligrafy-new). Documents (DOCS) changed by hand are left alone
- composer.json gets the new packages and versions the project did not change (then composer update for them), .gitignore the new lines, .env the new settings with their defaults (existing values are never changed)
- php caligrafer.php upgrade --apply, from the new release, brings application/ up to date Everything replaced is copied first to backups/caligrafy-update-<from>-to-<to>-<time>/. --check reports only. application/, .env values, public/uploads and vendor/ are never replaced.
REPOSITORY = 'caligrafy/caligrafy-quill'WHOLE = array('framework', 'claude-plugin')KEEP = array('framework/librairies/app/.env')SKIP = ...MERGED = array('composer.json', '.gitignore')DOCS = array('README.md', 'CHANGELOG.md', 'CONTRIBUTING.md', 'CODE_OF_CONDUCT.md', 'ARCHITECTURE.md', 'LICENSE')NEW_SUFFIX = '.caligrafy-new'ENV_EXAMPLE = 'framework/settings/.env.example'
public function __construct($root = '.', $fetch = null, $run = null)Parameters
-
$rootstring, default'.': the project -
$fetchcallable|null, defaultnull: fn(string $version): string folder of that release, null when it does not exist; fn('latest') gives the latest version number (tests) -
$runcallable|null, defaultnull: fn(string $command): int, runs a shell command in the project (tests)
Source: framework/core/Updater.php:59
public function run($target = null, $check = false, $yes = false, $composer = true)Parameters
-
$targetstring|null, defaultnull: a version (2.8.0, v2.8.0) or null for the latest -
$checkbool, defaultfalse: report only -
$yesbool, defaultfalse: go on although git has uncommitted changes -
$composerbool, defaulttrue: run composer update for changed packages
Returns int: exit code
Source: framework/core/Updater.php:74
public function newerRelease()The latest release when it is newer than the project's, for upgrade: [version or null, why it could not tell or null]
Source: framework/core/Updater.php:350
public function download($version)The release folder of a version, downloaded from GitHub and unpacked in a temporary folder; 'latest' gives the latest version number
Parameters
$version
Source: framework/core/Updater.php:368
namespace Caligrafy;
class UpgraderSource: framework/core/Upgrader.php:18
Class Upgrader Compares application/ with framework/settings/application and reports or applies the template updates. Only template files are touched; files you created, .env, public/uploads and vendor are never read or written. Client apps in public/ created from framework/plugins are reported only, and so are .env (new and obsolete settings) and the views or apps that still use Bootstrap 3 classes (the framework uses Bootstrap 5).
TEMPLATE = 'framework/settings/application'HISTORY = 'framework/settings/template-history.json'USER_SERVICES = 'services/Users/'ADMIN_CONTROLLER = 'controllers/core/Auth/AdminController.php'AUTH_CONTROLLER = 'controllers/core/Auth/AuthController.php'PLUGINS = 'framework/plugins'MANIFEST = '.caligrafy-manifest.json'ROUTES = 'config/routing/web.php'NEW_SUFFIX = '.caligrafy-new'MANIFEST_FORMAT = 1PLUGIN_MARKERS =['facedetect' => 'new FaceDetector', 'ml' => 'new MlCore', 'bot' => '__bots__', 'app' => 'loadVue(']BOOTSTRAP3 = ...OBSOLETE_SETTINGS =['CRYPTO_PAY_KEY' => 'Coinbase Commerce closed on March 31, 2026: use COINBASE_API_KEY_NAME and COINBASE_API_KEY_SECRET (Coinbase Business)']
-
public static $composer = null: callable|null tests: fn(string $command): int, runs composer instead of the shell
public function __construct($root = '.', $apply = false, $force = null, $keep = array())Parameters
-
$root, default'.' -
$apply, defaultfalse -
$force, defaultnull -
$keeparray, defaultarray(): application/ paths to keep even though they are untouched copies of an older template (a default page that became a page of the site, like the home page): the new version is written next to them, as for customized files
Source: framework/core/Upgrader.php:51
public static function hashes($dir)sha256 of every file of a folder, keyed by relative path (.DS_Store and node_modules skipped)
Parameters
$dir
Source: framework/core/Upgrader.php:64
public static function writeManifest($root = '.', $files = null, $kept = array())Writes application/.caligrafy-manifest.json (called by initialize after copying the templates)
Parameters
-
$root, default'.' -
$files, defaultnull -
$kept, defaultarray()
Source: framework/core/Upgrader.php:81
public function plan()Compares every template file with application/
Returns array: path => ['status' => missing|identical|outdated|customized|routes, 'template' => hash, 'current' => hash|null]
Source: framework/core/Upgrader.php:101
public function run()Prints the plan and, with --apply or --force, applies it
Source: framework/core/Upgrader.php:199
namespace Caligrafy;
class DeployerSource: framework/core/Deployer.php:23
Class Deployer
- waits for the database (a new server starts its database at the same time as the app)
- creates the framework tables (framework/settings/platform.sql, safe to run again)
- runs every application/models/<folder>/create_db.sql that is new or changed since its last run, and records it in caligrafy_scripts. A script that drops, deletes or truncates is never run again once it ran: run it by hand (php caligrafer.php sql <file>) when you mean it. seed.sql and other scripts are never run. A statement that adds a column, an index or a foreign key already there (the script's earlier run added it) is skipped, so a script keeps its ALTER TABLE ... ADD statements.
- runs the migrations of application/migrations that never ran (Migrations, 2.18.0) Without DB_ACTIVATE=true there is nothing to do.
SCRIPTS_TABLE = 'caligrafy_scripts'DESTRUCTIVE = '/\b(DROP\s+(TABLE|DATABASE|SCHEMA|VIEW|COLUMN)|DELETE\s+FROM|TRUNCATE)\b/i'
->__construct()->run()Deployer::cacheEnv()->schedule()->scripts()Deployer::destructive()Deployer::alreadyApplied()
public function __construct($root = '.', $wait = 60)Parameters
-
$rootstring, default'.': the project folder -
$waitint, default60: seconds to wait for the database
Source: framework/core/Deployer.php:36
public function run()Returns int: exit code: 0 when the project is ready, 1 when the database could not be prepared
Source: framework/core/Deployer.php:45
public static function cacheEnv($root = '.')Compiles .env to .env.php (2.19.0): the values Dotenv reads from it, as a PHP array that index.php includes (OPcache keeps it) instead of parsing .env on every request. Same owner, group and permissions as .env; written to a temporary file, then renamed. index.php uses it only while .env still has the content it was compiled from (its sha1).
Parameters
-
$root, default'.'
Returns bool: false without a .env
Source: framework/core/Deployer.php:81
public function schedule()Prints the scheduled jobs (application/config/schedule.php) on one line of the start-up log, where Caligrafy Cloud reads them: "caligrafy-schedule <the listing of schedule list --json>", or "caligrafy-schedule-invalid <the problems>" (the jobs then do not run). Without the file the listing is empty.
Source: framework/core/Deployer.php:115
public function scripts()application/models/<folder>/create_db.sql files, in folder order
Source: framework/core/Deployer.php:130
public static function destructive($sql)Whether a script drops, deletes or truncates data
Parameters
$sql
Source: framework/core/Deployer.php:140
public static function alreadyApplied(\Throwable $e)Whether a statement failed only because what it adds is already there (MySQL and MariaDB: duplicate column 1060, duplicate key name 1061, duplicate foreign key 1826), so a changed create_db.sql can keep its ALTER TABLE ... ADD statements and run again
Parameters
-
$e\Throwable
Source: framework/core/Deployer.php:211
namespace Caligrafy;
class CloudClientSource: framework/core/CloudClient.php:19
Class CloudClient
Calls the capabilities of Caligrafy Cloud (POST <url>/capabilities/<name>) with a personal access token of the
account. cloud login gets it by itself: it opens <url>/cli?code=..., the owner approves the code shown in the
terminal, and the command collects the token (start_cli_login, poll_cli_login). --token or --paste take one created
on the dashboard instead. The token is kept in .caligrafy/cloud.json of the project (git-ignored, never in the
image), or given with CALIGRAFY_CLOUD_TOKEN. CALIGRAFY_CLOUD_URL points to another service.
DEFAULT_URL = 'https://deploy.caligrafy.com'PERIODS = array('week', 'month', 'year')
public function __construct($root = '.', $http = null, $sleep = null)Parameters
-
$root, default'.' -
$httpcallable|null, defaultnull: fn(string $method, string $url, array $headers, ?string $body): [int $status, string $body] (tests) -
$sleepcallable|null, defaultnull: fn(int $seconds) between two checks of a sign-in or a deployment (tests)
Source: framework/core/CloudClient.php:32
public function run(array $args)Parameters
-
$argsarray: the words after "cloud"
Returns int: exit code
Source: framework/core/CloudClient.php:43
public function call($capability, array $input = array(), $signedIn = true)Calls a capability: returns its data, or throws a RuntimeException with its message (and field messages)
Parameters
$capability-
$inputarray, defaultarray() -
$signedIn, defaulttrue
Source: framework/core/CloudClient.php:161
namespace Caligrafy;
class QuickstartSource: framework/core/Quickstart.php:23
Class Quickstart Every step can be run again: it only does what is missing.
- application/ (initialize; it deletes the .git folder of the framework, so it needs --yes when one exists)
- .env: the database of the Docker server (DB_ACTIVATE=true, DB_HOST=mysql, a database name), or, on a local server (MAMP, XAMPP, LAMP), the address of the project in its web folder (APP_ROOT) and its MySQL settings (asked with the stack's defaults; without a terminal to confirm them, skipped unless --db-defaults)
- the database and the framework tables (tokens, audit log)
- web.php: Route::capabilities('/capabilities') for the JS client, Route::mcp('/mcp') for AI agents and Route::notifications() for the links of notification emails
- with --example: the Tasks example (capabilities, page, REST routes, table, JS client)
EXAMPLE = 'framework/settings/examples/tasks'LOCAL_STACKS = ...DEFAULT_DATABASE = 'caligrafy'-
DOCKER_SERVICES = array('dev-box' => array(80, '8080'), 'prod-box' => array(80, '8080'), 'phpmyadmin' => array(80, '8077'), 'mysql' => array(3306, '3306')): Runs the steps COMPOSE_FILES = ...
->__construct()Quickstart::dockerPort()Quickstart::publishedPort()Quickstart::composePort()->run()Quickstart::stacks()Quickstart::localSite()Quickstart::addRoutes()Quickstart::envSet()Quickstart::envValue()
public function __construct($root = '.', ?callable $out = null)No doc comment.
Parameters
-
$root, default'.' -
$out?callable, defaultnull
Source: framework/core/Quickstart.php:53
public static function dockerPort($root = '.', $service = 'dev-box')The port of this computer that reaches a service of the Docker server (dev-box by default; 2.21.8, 2.24.0): first what Docker published (running containers: every compose file, overrides and variables counted), else the compose files with their override (docker-compose.override.yml, which the projects of a computer use to move 8080, 8077 and 3306 when another project holds them), else the usual port
Parameters
-
$root, default'.' -
$servicestring, default'dev-box': dev-box, prod-box, phpmyadmin or mysql
Source: framework/core/Quickstart.php:77
public static function publishedPort($root, $service, $inside)The port Docker published for a service of this project (docker compose port), or null: Docker missing or off, the service not running, or inside a container (no docker command there)
Parameters
$root$service$inside
Source: framework/core/Quickstart.php:98
public static function composePort($compose, $service, $inside)The port of this computer a compose file maps to a service's port inside its container ("8170:80", "127.0.0.1:8170:80", "${WEB_PORT:-8080}:80"), or null when the file does not say
Parameters
$compose$service$inside
Source: framework/core/Quickstart.php:112
public function run(array $options = array())No doc comment.
Parameters
-
$optionsarray, defaultarray()
Source: framework/core/Quickstart.php:141
public static function stacks($mamp = '/Applications/MAMP')LOCAL_STACKS with what MAMP is really set to on this computer, when it is installed: its web folder (Apache's DocumentRoot), its address (Listen) and its MySQL port (--port). Many installs serve another folder than htdocs and use 80 and 3306 instead of 8888 and 8889
Parameters
-
$mamp, default'/Applications/MAMP'
Source: framework/core/Quickstart.php:322
public static function localSite($dir, array $stacks)Where a folder is served by a known local server: ['name', 'url', 'appRoot', 'db'], or null outside their web folders
Parameters
-
$dirstring: the real path of the project -
$stacksarray: LOCAL_STACKS
Source: framework/core/Quickstart.php:348
public static function addRoutes($source, array $lines, $comment)Inserts lines before Route::run() in a web.php, under a comment, unless every line is already there
Parameters
-
$sourcestring: the content of web.php -
$linesarray: the route lines (without indentation) -
$commentstring: one line that says what they are
Returns string: the new content (the same when nothing was missing)
Source: framework/core/Quickstart.php:425
public static function envSet($env, array $values)Sets KEY=value lines in the content of a .env (replaces the line, or adds it at the end)
Parameters
$env-
$valuesarray
Source: framework/core/Quickstart.php:455
public static function envValue($env, $key)The value of a KEY in the content of a .env (without an inline # comment), '' when it is not set
Parameters
$env$key
Source: framework/core/Quickstart.php:471
namespace Caligrafy;
class PluginSourceSource: framework/core/PluginSource.php:22
Class PluginSource A project enables the Claude Code plugin of its own claude-plugin/ folder through .claude/settings.json: a plugin source (a "marketplace", declared in .claude-plugin/marketplace.json) at "./", and the plugin caligrafy-quill@<name>. Claude Code keeps one source per name for the whole computer: when every project calls it "caligrafy", the first project opened wins, every other project gets that project's plugin (an older one), and none gets any once that folder is deleted. initialize and update give the project its own name: caligrafy-<folder>-<4 hex>. The framework's own repository keeps "caligrafy": it is the source people add from GitHub. Claude Code runs a copy of the plugin it makes at install time (~/.claude/plugins/cache), not the project's folder: after an update brings a newer claude-plugin/, connect() refreshes that copy, and doctor warns while it is older.
SHARED = 'caligrafy'PLUGIN = 'caligrafy-quill'SETTINGS = '.claude/settings.json'MARKETPLACE = '.claude-plugin/marketplace.json'MANIFEST = 'claude-plugin/.claude-plugin/plugin.json'
PluginSource::name()PluginSource::ensureOwn()PluginSource::registeredPath()PluginSource::projectVersion()PluginSource::installedVersion()PluginSource::connect()PluginSource::isFramework()
public static function name($root = '.')The name of the project's plugin source, or null when the project has no plugin source files
Parameters
-
$root, default'.'
Source: framework/core/PluginSource.php:33
public static function ensureOwn($root = '.')Renames a shared "caligrafy" source to the project's own name, in both files
Parameters
-
$root, default'.'
Returns string|null: the new name, or null when there was nothing to rename
Source: framework/core/PluginSource.php:43
public static function registeredPath($name, $home = null)Where Claude Code on this computer reads the source of that name (~/.claude/plugins/known_marketplaces.json), or null when it does not know it yet
Parameters
$name-
$home, defaultnull
Source: framework/core/PluginSource.php:75
public static function projectVersion($root = '.')The version of the plugin in the project's claude-plugin/ folder, or null
Parameters
-
$root, default'.'
Source: framework/core/PluginSource.php:89
public static function installedVersion($name, $root = '.', $home = null)The version of the copy Claude Code runs for this project (~/.claude/plugins/installed_plugins.json: installed for the project, else for the user), or null when the plugin is not installed
Parameters
$name-
$root, default'.' -
$home, defaultnull
Source: framework/core/PluginSource.php:99
public static function connect($root, $say, $run = null)Registers the project's plugin source with Claude Code on this computer and installs the plugin for the project (claude plugin marketplace add <folder>, claude plugin install caligrafy-quill@<name> --scope project). An already trusted folder is never asked again, so starting claude there does not register a renamed source
Parameters
$root-
$saycallable: fn(string $line) -
$runcallable|null, defaultnull: fn(string $command): [int $code, string $output] (tests)
Returns int: exit code
Source: framework/core/PluginSource.php:127
public static function isFramework($root = '.')No doc comment.
Parameters
-
$root, default'.'
Caligrafy: Describe it. Approve it. Ship it. Your idea turned into a complete app: every feature built once, for web pages, Vue apps, a REST API and AI agents, and online.
Why Caligrafy: the benefits, the measurements, and what it is not yet
-
Quickstart with Claude: describe it, approve it, ship it
or the Quickstart: the same three steps by hand, in 5 minutes
- How Caligrafy works: the ideas in 5 minutes
- Your first capability: build a feature, page included
-
Capabilities in depth
-
#[Capability]: every option - Scheduled jobs: capabilities on a timer
- Notifications: web push and email
- Background jobs: capabilities run later
- Migrations: database changes, in order
- The cache
-
Testing your app
- Dependency injection
- Validation rules
-
Web pages
- Views and page components
- Design styles: another look, one setting
- Declarative routes: every request, in one line
-
Route::page·Route::form·Route::auth·Route::webhook·Route::download·Route::redirect·Route::action·Route::snap· capability routes - How to export a list as CSV
- Controllers: moving one to a declared route · how to, step by step
- Forms and helpers
-
Vue apps
-
Mobile and tablet
-
REST API
-
AI agents
-
Snap Cards and the assistant
- Payments: Stripe and crypto
- Face detection and face login
- OpenAI: chat and images · Assistants
- Open models: Llama, Mistral...
- Chatbots with Watson
- Machine learning in the browser
- Metadata and social cards · search and analytics
-
Putting it online: the production image, the database at every start
-
API reference: every class and method
-
Routing ·
Route·AuthRoutes· pages ·Controller· capabilities -
Data ·
Model·Query· sign-in · jobs ·Notify· payments · AI -
Interfaces · Snap ·
Snap\Cards·Snap\Conversation· testing · tooling · helper functions
-
Routing ·
-
Caligrafer: every command
- Project · build · data · agents · jobs and tests · maintenance · deploy
- Installation, configuration and Caligrafer
- Routing · Request · Controllers · Views
- Vue: flow of information · routes · validations
- Security and upgrading
- Release notes: 2.26 · 2.25 · 2.24 · 2.23 · 2.22 · 2.21 · 2.20 · 2.19 · 2.18 · 2.17 · 2.16 · 2.15 · 2.14 · 2.13 · 2.12 · 2.11 · 2.10 · 2.9 · 2.8 · 2.7 · 2.6 · 2.5 · 2.4 · 2.3 · 2.1 · October 2026 · changelog
- Testing Caligrafy (contributors)