Skip to content

API Tooling

Dory Azar edited this page Nov 15, 2026 · 2 revisions

Framework and 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 ...

class Caligrafy\Features

namespace Caligrafy;

class Features

Source: 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.

Constants

  • 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 properties

  • 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)

Methods

Features::names()

public static function names()

The feature names: payments, ai, push, passkeys, snap (SDKs) and facedetect (files)

Source: framework/core/Features.php:141

Features::installedPackage()

public static function installedPackage($package)

Whether a package is installed (its class loads)

Parameters

  • $package

Source: framework/core/Features.php:149

Features::missing()

public static function missing($feature)

The packages of a feature that are not installed: [name => constraint]

Parameters

  • $feature

Source: framework/core/Features.php:159

Features::installed()

public static function installed($feature)

No doc comment.

Parameters

  • $feature

Source: framework/core/Features.php:165

Features::need()

public static function need($feature, $what)

Throws FeatureMissing when the feature's SDK is not installed

Parameters

  • $feature
  • $what string: what needs it (Caligrafy\Payment, new Agent() with Groq...)

Source: framework/core/Features.php:174

Features::message()

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, default null

Source: framework/core/Features.php:183

Features::requireCommand()

public static function requireCommand(array $features)

The composer require command of features: composer require --no-interaction 'a/b:^1' ...

Parameters

  • $features array

Source: framework/core/Features.php:193

Features::used()

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 '.'
  • $env callable|null, default null: fn(string $name): string, a setting (default: the project's .env file)

Source: framework/core/Features.php:209

Features::toRestore()

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
  • $composer array
  • $env, default null

Source: framework/core/Features.php:242

Features::add()

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

  • $features array
  • $root, default '.'
  • $run callable|null, default null: fn(string $command): int (tests fake Composer)
  • $out callable|null, default null: fn(string $line)

Returns int: exit code

Source: framework/core/Features.php:260

Features::facedetectReady()

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

Features::modelFiles()

public static function modelFiles($root = '.')

The model files: [path, sha256, size, urls]

Parameters

  • $root, default '.'

Source: framework/core/Features.php:318

Features::pictureFiles()

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

Features::download()

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

  • $files array

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

class Caligrafy\FeatureMissing

namespace Caligrafy;

class FeatureMissing extends \LogicException

Source: 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.

class Caligrafy\Framework

namespace Caligrafy;

class Framework extends \stdClass

Source: framework/core/Framework.php:19

Class Framework that loads the entire framework Framework loader

Constants

  • VERSION = '2.26.0': Version of Caligrafy Quill (semantic versioning: the git tag is v<VERSION>, the changes are in CHANGELOG.md)

Methods

Framework::run()

public static function run()

Framework main

Source: framework/core/Framework.php:29

Framework::mappedFolders()

public static function mappedFolders()

The folders of the class map: the framework's classes and the application's

Source: framework/core/Framework.php:78

Framework::unmappedFolders()

public static function unmappedFolders()

Left out of the class map: settings and routes (loaded by dispatch), views (Pug)

Source: framework/core/Framework.php:86

class Caligrafy\Loader

namespace Caligrafy;

class Loader extends \stdClass

Source: 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

Methods

->library()

public function library($lib)

No doc comment.

Parameters

  • $lib

Source: framework/core/Loader.php:23

->helper()

public function helper($helper)

No doc comment.

Parameters

  • $helper

Source: framework/core/Loader.php:28

class Caligrafy\ClassMap

namespace Caligrafy;

class ClassMap

Source: 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.

Constants

  • VERSION = 2
  • PRODUCTION_CHECK = 60

Methods

ClassMap::enabled()

public static function enabled()

Whether the class map is on (CLASS_MAP=false turns it off)

Source: framework/core/ClassMap.php:38

ClassMap::register()

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

  • $dirs array: absolute folders to map
  • $skip array, default array(): absolute folders inside them to leave out

Source: framework/core/ClassMap.php:48

ClassMap::load()

public static function load($class)

The autoloader

Parameters

  • $class

Source: framework/core/ClassMap.php:78

ClassMap::capabilityFiles()

public static function capabilityFiles($fresh = false)

The files that may declare capabilities (they mention #[Capability]): Capabilities loads them to find every one

Parameters

  • $fresh, default false

Source: framework/core/ClassMap.php:93

ClassMap::refresh()

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

ClassMap::registered()

public static function registered()

Whether the autoloader is registered

Source: framework/core/ClassMap.php:133

ClassMap::map()

public static function map()

The map, from memory, the cached file or a new scan

Source: framework/core/ClassMap.php:141

ClassMap::rebuild()

public static function rebuild()

Scans the folders again and saves the map

Source: framework/core/ClassMap.php:158

ClassMap::declarations()

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

ClassMap::tempFolder()

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

class Caligrafy\Caligrafer

namespace Caligrafy;

class Caligrafer extends \stdClass

Source: 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

Methods

Caligrafer::run()

public static function run()

Caligrafer main

Source: framework/core/Caligrafer.php:25

Caligrafer::generateKeys()

public static function generateKeys()

Generates Keys from the command line

Source: framework/core/Caligrafer.php:35

Caligrafer::generateApiKey()

public static function generateApiKey()

Generates Api Key from the command line

Source: framework/core/Caligrafer.php:49

Caligrafer::dispatch()

public static function dispatch()

Dispatches the URL to the route

Source: framework/core/Caligrafer.php:100

class Caligrafy\Doctor

namespace Caligrafy;

class Doctor

Source: 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.

Constants

  • CHECKS =['project', 'database', 'openai', 'groq', 'agent', 'stripe', 'coinbase', 'watson', 'mail']
  • LABELS = ...
  • TIMEOUT = 8
  • PLACEHOLDERS =['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_']

Methods

->__construct()

public function __construct($root = '.', $only = null, $envFile = '.env')

No doc comment.

Parameters

  • $root, default '.'
  • $only, default null
  • $envFile, default '.env'

Source: framework/core/Doctor.php:40

->run()

public function run($json = false)

Runs the checks and prints the report

Parameters

  • $json, default false

Returns int: exit code: 0 without FAIL, 1 otherwise

Source: framework/core/Doctor.php:51

->results()

public function results()

No doc comment.

Source: framework/core/Doctor.php:85

Doctor::formsWithoutMethod()

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

Doctor::pugPitfalls()

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

Doctor::mask()

public static function mask($value)

No doc comment.

Parameters

  • $value

Source: framework/core/Doctor.php:849

class Caligrafy\Updater

namespace Caligrafy;

class Updater

Source: 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:

  1. 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
  2. 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
  3. 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)
  4. 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.

Constants

  • 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'

Methods

->__construct()

public function __construct($root = '.', $fetch = null, $run = null)

Parameters

  • $root string, default '.': the project
  • $fetch callable|null, default null: fn(string $version): string folder of that release, null when it does not exist; fn('latest') gives the latest version number (tests)
  • $run callable|null, default null: fn(string $command): int, runs a shell command in the project (tests)

Source: framework/core/Updater.php:59

->run()

public function run($target = null, $check = false, $yes = false, $composer = true)

Parameters

  • $target string|null, default null: a version (2.8.0, v2.8.0) or null for the latest
  • $check bool, default false: report only
  • $yes bool, default false: go on although git has uncommitted changes
  • $composer bool, default true: run composer update for changed packages

Returns int: exit code

Source: framework/core/Updater.php:74

->newerRelease()

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

->download()

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

class Caligrafy\Upgrader

namespace Caligrafy;

class Upgrader

Source: 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).

Constants

  • 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 = 1
  • PLUGIN_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 properties

  • public static $composer = null: callable|null tests: fn(string $command): int, runs composer instead of the shell

Methods

->__construct()

public function __construct($root = '.', $apply = false, $force = null, $keep = array())

Parameters

  • $root, default '.'
  • $apply, default false
  • $force, default null
  • $keep array, default array(): 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

Upgrader::hashes()

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

Upgrader::writeManifest()

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, default null
  • $kept, default array()

Source: framework/core/Upgrader.php:81

->plan()

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

->run()

public function run()

Prints the plan and, with --apply or --force, applies it

Source: framework/core/Upgrader.php:199

class Caligrafy\Deployer

namespace Caligrafy;

class Deployer

Source: framework/core/Deployer.php:23

Class 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 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.
  4. runs the migrations of application/migrations that never ran (Migrations, 2.18.0) Without DB_ACTIVATE=true there is nothing to do.

Constants

  • SCRIPTS_TABLE = 'caligrafy_scripts'
  • DESTRUCTIVE = '/\b(DROP\s+(TABLE|DATABASE|SCHEMA|VIEW|COLUMN)|DELETE\s+FROM|TRUNCATE)\b/i'

Methods

->__construct()

public function __construct($root = '.', $wait = 60)

Parameters

  • $root string, default '.': the project folder
  • $wait int, default 60: seconds to wait for the database

Source: framework/core/Deployer.php:36

->run()

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

Deployer::cacheEnv()

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

->schedule()

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

->scripts()

public function scripts()

application/models/<folder>/create_db.sql files, in folder order

Source: framework/core/Deployer.php:130

Deployer::destructive()

public static function destructive($sql)

Whether a script drops, deletes or truncates data

Parameters

  • $sql

Source: framework/core/Deployer.php:140

Deployer::alreadyApplied()

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

class Caligrafy\CloudClient

namespace Caligrafy;

class CloudClient

Source: 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.

Constants

  • DEFAULT_URL = 'https://deploy.caligrafy.com'
  • PERIODS = array('week', 'month', 'year')

Methods

->__construct()

public function __construct($root = '.', $http = null, $sleep = null)

Parameters

  • $root, default '.'
  • $http callable|null, default null: fn(string $method, string $url, array $headers, ?string $body): [int $status, string $body] (tests)
  • $sleep callable|null, default null: fn(int $seconds) between two checks of a sign-in or a deployment (tests)

Source: framework/core/CloudClient.php:32

->run()

public function run(array $args)

Parameters

  • $args array: the words after "cloud"

Returns int: exit code

Source: framework/core/CloudClient.php:43

->call()

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
  • $input array, default array()
  • $signedIn, default true

Source: framework/core/CloudClient.php:161

class Caligrafy\Quickstart

namespace Caligrafy;

class Quickstart

Source: framework/core/Quickstart.php:23

Class Quickstart Every step can be run again: it only does what is missing.

  1. application/ (initialize; it deletes the .git folder of the framework, so it needs --yes when one exists)
  2. .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)
  3. the database and the framework tables (tokens, audit log)
  4. web.php: Route::capabilities('/capabilities') for the JS client, Route::mcp('/mcp') for AI agents and Route::notifications() for the links of notification emails
  5. with --example: the Tasks example (capabilities, page, REST routes, table, JS client)

Constants

  • 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 = ...

Methods

->__construct()

public function __construct($root = '.', ?callable $out = null)

No doc comment.

Parameters

  • $root, default '.'
  • $out ?callable, default null

Source: framework/core/Quickstart.php:53

Quickstart::dockerPort()

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 '.'
  • $service string, default 'dev-box': dev-box, prod-box, phpmyadmin or mysql

Source: framework/core/Quickstart.php:77

Quickstart::publishedPort()

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

Quickstart::composePort()

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

->run()

public function run(array $options = array())

No doc comment.

Parameters

  • $options array, default array()

Source: framework/core/Quickstart.php:141

Quickstart::stacks()

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

Quickstart::localSite()

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

  • $dir string: the real path of the project
  • $stacks array: LOCAL_STACKS

Source: framework/core/Quickstart.php:348

Quickstart::addRoutes()

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

  • $source string: the content of web.php
  • $lines array: the route lines (without indentation)
  • $comment string: one line that says what they are

Returns string: the new content (the same when nothing was missing)

Source: framework/core/Quickstart.php:425

Quickstart::envSet()

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
  • $values array

Source: framework/core/Quickstart.php:455

Quickstart::envValue()

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

class Caligrafy\PluginSource

namespace Caligrafy;

class PluginSource

Source: 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.

Constants

  • SHARED = 'caligrafy'
  • PLUGIN = 'caligrafy-quill'
  • SETTINGS = '.claude/settings.json'
  • MARKETPLACE = '.claude-plugin/marketplace.json'
  • MANIFEST = 'claude-plugin/.claude-plugin/plugin.json'

Methods

PluginSource::name()

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

PluginSource::ensureOwn()

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

PluginSource::registeredPath()

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, default null

Source: framework/core/PluginSource.php:75

PluginSource::projectVersion()

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

PluginSource::installedVersion()

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, default null

Source: framework/core/PluginSource.php:99

PluginSource::connect()

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
  • $say callable: fn(string $line)
  • $run callable|null, default null: fn(string $command): [int $code, string $output] (tests)

Returns int: exit code

Source: framework/core/PluginSource.php:127

PluginSource::isFramework()

public static function isFramework($root = '.')

No doc comment.

Parameters

  • $root, default '.'

Source: framework/core/PluginSource.php:183

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.

Start here

Why Caligrafy: the benefits, the measurements, and what it is not yet

  1. Quickstart with Claude: describe it, approve it, ship it

    or the Quickstart: the same three steps by hand, in 5 minutes

  2. How Caligrafy works: the ideas in 5 minutes
  3. Your first capability: build a feature, page included

Capabilities

Experiences

Data and people

  1. Models
  2. Relationships
  3. Queries: filters, paging, related rows
  4. Sign-in, users and tokens

Built-in features

Put it online

Build with AI

Reference

Clone this wiki locally