# caligrafy

Adopt this skill: https://proskills.md/skills/other/caligrafy-caligrafy-quill-claude-plugin-skills-caligrafy

Page: https://proskills.md/skills/other/caligrafy-caligrafy-quill-claude-plugin-skills-caligrafy
This document is the public skill bundle for that page. It contains SKILL.md and every helper file the catalog carries. No login is required.

## File: claude-plugin/skills/caligrafy/SKILL.md
size: 27079
sha: 1707227a996bc65314978bf773e87949590b76c7
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/SKILL.md

````text
---
name: caligrafy
description: Expert knowledge of the Caligrafy Quill PHP MVC framework (Caligrafy\Controller, Caligrafy\Model, Route, Request, Auth, Pug/Phug views, GUMP validation, caligrafer CLI, Docker dev server, Vue client apps, Stripe/Coinbase payments, OpenAI/Groq/Watson AI, open models (Llama) through Together, Fireworks, OpenRouter or Ollama, FaceDetect, ML, and the build-once layer: services and capabilities served to web pages, REST, the JS client and AI agents over MCP, with personal access tokens and an audit log, and the page components every new UI is built with). Use whenever code touches a Caligrafy Quill project - application/config/routing/web.php, application/controllers, application/models, application/views/*.pug, public/<app>/, .env, caligrafer.php or .bin/caligrafer.
when_to_use: The working directory contains caligrafer.php, framework/core/Controller.php or application/config/routing/web.php; the user mentions Caligrafy, Quill, caligrafer, Phug/pug views, Caligrafy Vue apps, FaceDetect or MlCore; or asks to add routes, controllers, models, REST APIs, capabilities, MCP tools for agents, tokens, auth, payments, AI features, or to debug [ERROR 1001/1004/2003/3000] messages.
---

# Caligrafy Quill

Caligrafy Quill is "Describe it. Approve it. Ship it.": an idea turned into a complete app, built once and experienced everywhere. It is a PHP MVC framework (PHP >= 8.3, MySQL via PDO) with Pug templates (Phug), GUMP validation,
a REST API built into every route, and Vue 3 client apps. It covers the whole way from zero to one in one project:
start (`quickstart`), data, capabilities written once, every experience (pages, Vue and phone apps, REST, MCP agents),
people and trust, ship (tests, `doctor`, review), online (`Dockerfile`, `caligrafer cloud deploy`). When a user
describes an idea, take it along these steps (the `idea` skill does all of them); when a feature works locally, the next step is the `deploy` skill. This skill is an index: read the reference file
for the topic before writing code, and check [gotchas.md](reference/gotchas.md) whenever behavior surprises you.
Signatures in [api-reference.md](reference/api-reference.md) are copied from the source and win over the wiki.

## Reference map

| Topic | Read |
| - | - |
| **Start here**: set up a project in one command (`.bin/caligrafer quickstart`), the ideas, a complete first feature | [15-quickstart.md](reference/15-quickstart.md), [16-how-caligrafy-works.md](reference/16-how-caligrafy-works.md), [17-your-first-capability.md](reference/17-your-first-capability.md) |
| Learning Caligrafy from zero (a user asks for a tutorial or course): the Caligrafy course builds Bookshelf capabilities first in 15 lessons, up to putting it online, each with a manual track and a With Claude track (point the user to it; its lessons match these docs) | https://github.com/caligrafy/caligrafy-quill-course/wiki |
| Every Caligrafer command for a user (what it does, options, examples): quickstart, initialize, server, service, client, apps, sql, token, mcp, audit, doctor, upgrade, deploy, cloud | [18-caligrafer.md](reference/18-caligrafer.md) (exact behavior: the CLI table of api-reference.md) |
| Exact class/helper signatures, lifecycle, CLI, Docker | [api-reference.md](reference/api-reference.md) |
| Verified pitfalls and wiki errata (G1-G115) | [gotchas.md](reference/gotchas.md) |
| Overview / README | [00-home-overview.md](reference/00-home-overview.md) |
| Install, MVC concepts, naming conventions, file structure, caligrafer | [01-getting-started.md](reference/01-getting-started.md) |
| Routes, params, controller routes, method spoofing | [02-routing.md](reference/02-routing.md) |
| Request object | [03-request.md](reference/03-request.md) |
| Database activation, SQL schemas, models, CRUD | [04-models.md](reference/04-models.md) |
| hasOne / hasMany / belongsToMany, pivots | [05-relationships.md](reference/05-relationships.md) |
| GUMP validators and filters, check/validate/filter | [06-validation.md](reference/06-validation.md) |
| Pug views | [07-views.md](reference/07-views.md) |
| Controllers, associate(), built-in CRUD | [08-controllers.md](reference/08-controllers.md) |
| Helpers, HTML forms, CSRF, REST API + Postman | [09-helpers-forms-rest-api.md](reference/09-helpers-forms-rest-api.md) |
| Authentication module | [09.1-authentication.md](reference/09.1-authentication.md) |
| Production checklist, server-file protection, upgrading old projects, behavior changes | [09.5-security-and-upgrading.md](reference/09.5-security-and-upgrading.md) |
| Build once: services, `#[Capability]`, REST (`Route::capability`), the JS client, personal access tokens, MCP for agents, audit log, design tokens | [13-build-once-services-capabilities-mcp.md](reference/13-build-once-services-capabilities-mcp.md), [api-reference.md](reference/api-reference.md) (Build once section), G50-G58 |
| This Claude Code plugin (commands, agent, install) | [10-claude-code-plugin.md](reference/10-claude-code-plugin.md) |
| Upgrading a project to 2.1 (build once): checklist, what is new, behavior changes, the legacy API key | [14-release-notes-2.1.0.md](reference/14-release-notes-2.1.0.md) |
| **A user describes an application to build** (an idea, "build me an app that..."): describe it, approve it, ship it with the `idea` skill (the Quickstart with Claude) | [22-quickstart-with-claude.md](reference/22-quickstart-with-claude.md), the `idea` skill |
| **Putting a project online**: the production `Dockerfile`, `caligrafer deploy` (database at every start), Caligrafy Cloud (`caligrafer cloud`, https://<name>.caligrafy.com, trial and credits), own server (Coolify, Docker); use the `deploy` skill | [20-putting-it-online.md](reference/20-putting-it-online.md), G67-G73 |
| What 2.4.0 changed: production image, `deploy`, `cloud`, sessions volume, the sign-in look of the page components, upgrading from 2.3.0 | [21-release-notes-2.4.0.md](reference/21-release-notes-2.4.0.md) |
| What 2.3.0 changed: `caligrafer call`, the upgrade reports and `--keep`, doctor warnings, fixed templates, one time zone, upgrading from 2.2.0 | [19-release-notes-2.3.0.md](reference/19-release-notes-2.3.0.md) |
| Every change of October 2026: upgrade checklist for existing projects, what can break, `.env` settings | [11-release-notes-october-2026.md](reference/11-release-notes-october-2026.md) |
| How the framework itself is built: the request lifecycle, `Capabilities::call`, the `Context` (actors, scopes, levels), each experience's adapter, the design decisions | `ARCHITECTURE.md` at the root of the framework (and of every project) |
| Changing the framework itself: QA suite phases, PHPUnit tests in `qa/unit`, mocks, adding a test, release process | [12-testing-caligrafy.md](reference/12-testing-caligrafy.md) |
| Stripe and Coinbase payments | [09.2-stripe-crypto-payment.md](reference/09.2-stripe-crypto-payment.md) |
| Metadata, rich cards, SEO, analytics | [09.3-metadata-rich-cards.md](reference/09.3-metadata-rich-cards.md), [09.4-search-referencing-analytics.md](reference/09.4-search-referencing-analytics.md) |
| Vue as a library (`caligrafer app`): setup, flow, structure, routes, requests, forms, validation, components | [09.7.0](reference/09.7.0-vue-as-library-setup.md), [09.7](reference/09.7-vue-flow-of-information.md), [09.7.1](reference/09.7.1-vue-structure.md), [09.7.2](reference/09.7.2-vue-routes.md), [09.7.3](reference/09.7.3-vue-requests.md), [09.7.4](reference/09.7.4-vue-forms.md), [09.7.5](reference/09.7.5-vue-validations.md), [09.7.6](reference/09.7.6-vue-components.md) |
| Vue SPA (`caligrafer create`): Vite 8 setup, Vue Router 5, SFC, Pinia | [09.8.1](reference/09.8.1-vue-spa-setup.md), [09.8.2](reference/09.8.2-vue-spa-routing.md), [09.8.3](reference/09.8.3-vue-single-file-components.md), [09.8.4](reference/09.8.4-vue-state-management.md) |
| Watson bots | [09.9.1-ai-watson-bots.md](reference/09.9.1-ai-watson-bots.md) |
| FaceDetect (@vladmandic/face-api) | [09.9.2-ai-face-detection.md](reference/09.9.2-ai-face-detection.md) |
| ML Core (ml5 + p5) | [09.9.3-ai-machine-learning.md](reference/09.9.3-ai-machine-learning.md) |
| OpenAI chat/images, Assistants, open source agents (Groq, Together, Fireworks, OpenRouter, Ollama) | [09.9.4](reference/09.9.4-ai-openai-chatgpt-dalle.md), [09.9.5](reference/09.9.5-ai-openai-assistants.md), [09.9.6](reference/09.9.6-ai-open-source-agents-groq.md) |
| Recipe: OpenAI/open model chat app (history, live model list, images, vision, Llama on OpenRouter/Ollama, Vue chat UI) | [recipes-ai-apps.md](reference/recipes-ai-apps.md) |
| Recipe: face login (liveness challenges, single-use nonce, anti-replay, encrypted prints, lockout, passkeys / WebAuthn step-up) | [recipes-face-login.md](reference/recipes-face-login.md) |
| What 2.6.0 changed: the quickstart from Composer on MAMP, XAMPP, LAMP or Docker (`composer create-project`, `created`, `.bin/caligrafer quickstart --local/--docker`, local addresses and MySQL settings) | [24-release-notes-2.6.0.md](reference/24-release-notes-2.6.0.md), G86 |
| What 2.7.0 changed: pages call their routes with the session (`apiHeaders()`, `X-CSRF-Token`), `cloud login` approved in the browser, `cloud key`, 30 free minutes per new app, the `ship` skill | [25-release-notes-2.7.0.md](reference/25-release-notes-2.7.0.md), G87-G90 |
| What 2.8.0 changed: design styles (`framework/design/`, `DESIGN_STYLE`, `php caligrafer.php design`, the `design` skill) | [27-release-notes-2.8.0.md](reference/27-release-notes-2.8.0.md), G92-G93 |
| What 2.9.0 changed: `php caligrafer.php update` (one command to the latest release, keeping what the project changed), the `update` skill | [28-release-notes-2.9.0.md](reference/28-release-notes-2.9.0.md), G94 |
| What 2.10.0 changed: `php caligrafer.php upgrade` checks for a newer release first and applies it like `update` (`--local`: templates only) | [29-release-notes-2.10.0.md](reference/29-release-notes-2.10.0.md), G96 |
| What 2.11.0 changed: Caligrafy's new look on every out-of-the-box page (`site.pug`, `layout.css`, `look.css`, `camera.css`, the Caligrafy mark), full-screen camera apps, the SPA template, Stripe Checkout on new accounts, the Payment Element in the page's colors | [30-release-notes-2.11.0.md](reference/30-release-notes-2.11.0.md), G73, G98-G101 |
| What 2.12.0 changed: a list and its detail side by side (`.cq-split > section.cq-split-main + aside.cq-split-detail`), used by the course's finder and SPA | [31-release-notes-2.12.0.md](reference/31-release-notes-2.12.0.md), [ui-guidelines.md](reference/ui-guidelines.md#a-list-and-its-detail-side-by-side-2120) |
| What 2.13.0 changed: selling an app on the marketplace of caligrafy.com: the `marketplace` skill (reads the code, walks the app for its screenshots, writes `.caligrafy/marketplace.json`), `php caligrafer.php cloud submit <name>` | [32-release-notes-2.13.0.md](reference/32-release-notes-2.13.0.md), [20-putting-it-online.md](reference/20-putting-it-online.md#marketplace), G102 |
| What 2.13.1 fixed: `update` and `plugin` refresh Claude Code's copy of the plugin (a new skill was missing after an update), `doctor` warns while it is older | [33-release-notes-2.13.1.md](reference/33-release-notes-2.13.1.md), G103 |
| What 2.13.2 changed: Describe it. Approve it. Ship it. in the plugin, `CLAUDE.md` and `AGENTS.md`; the `idea` skill stops for a review before shipping when asked, field messages under their fields | [34-release-notes-2.13.2.md](reference/34-release-notes-2.13.2.md) |
| What 2.13.3 fixed: a form with no method leaks its fields into the address when its script does not run; `doctor` warns | [35-release-notes-2.13.3.md](reference/35-release-notes-2.13.3.md), G104 |
| What 2.13.4 changed: Caligrafy accounts sign in with a confirmed email, *Forgot your password?* sends a 6-digit code; the `ship` skill guides the user | [36-release-notes-2.13.4.md](reference/36-release-notes-2.13.4.md), the `ship` skill |
| What 2.13.5 fixed: a changed `create_db.sql` keeps its `ALTER TABLE ... ADD` lines, `redirect()` to a full address, `doctor` warns about Pug's vanishing `$40` and one-line `- if` | [37-release-notes-2.13.5.md](reference/37-release-notes-2.13.5.md), G68, G105-G107 |
| What 2.13.6 changed: the QA suite takes the first free ports | [38-release-notes-2.13.6.md](reference/38-release-notes-2.13.6.md), G38 |
| **Scheduled jobs** (2.14.0): capabilities on a timer declared in `application/config/schedule.php` (`every` or `cron`, timezone, timeout), `php caligrafer.php schedule list/run/due/work/history`, `Context::system`, `caligrafy_job_runs`, the `scheduler` container, Caligrafy Cloud | [39-scheduled-jobs.md](reference/39-scheduled-jobs.md), [40-release-notes-2.14.0.md](reference/40-release-notes-2.14.0.md), [41-release-notes-2.14.1.md](reference/41-release-notes-2.14.1.md) (Caligrafy Cloud runs them: `cloud jobs`), [api-reference.md](reference/api-reference.md#scheduler), G108 |
| **Notifications** (2.15.0): `Caligrafy\Notify::user($userId, $message)` to web push and confirmed email, `public/js/notify.js` (`Notify.enable()` from a click), the `notify_*` capabilities, `Route::notifications()`, `php caligrafer.php notify keys`, emails in the design style, Caligrafy Cloud's keys and `APP_URL` | [42-notifications.md](reference/42-notifications.md), [43-release-notes-2.15.0.md](reference/43-release-notes-2.15.0.md), [api-reference.md](reference/api-reference.md#notify), G109-G111 |
| What 2.16.1 changed: worked examples of a job and a notification; the idea, design and deploy skills cover jobs, alerts and emails | [45-release-notes-2.16.1.md](reference/45-release-notes-2.16.1.md), G111 |
| What 2.16.0 changed: an unknown address answers 404 (405 with `Allow` for a wrong method), not the first route | [44-release-notes-2.16.0.md](reference/44-release-notes-2.16.0.md), G4 |
| **Pages and the rule "only capabilities touch models"** (2.5.0): `Route::page` (options, what the view gets, refusals), when to still write a controller, the user capabilities | [02-routing.md](reference/02-routing.md#pageroutes), [08-controllers.md](reference/08-controllers.md), [09.1-authentication.md](reference/09.1-authentication.md), [23-release-notes-2.5.0.md](reference/23-release-notes-2.5.0.md), G80-G85 |
| **Another look for every page**: a design style (`DESIGN_STYLE`, `php caligrafer.php design`, a `DESIGN.md` of `framework/design/` (vendored VoltAgent/awesome-design-md, local styles) or `application/design/`), one override stylesheet `public/css/designs/<name>.css`; use the `design` skill | [26-design-styles.md](reference/26-design-styles.md), [ui-guidelines.md](reference/ui-guidelines.md#design-styles), G92-G93 |
| **Any new page or client app UI**: Caligrafy's look (2.11.0+): `extends ../default/master/site`, the classes of `layout.css` (`.cq-hero`, `.cq-band`, `.cq-auth`, `.cq-form-grid`, `.cq-rows`, `.cq-split`, `.cq-tiles`, `.cq-account`...), `look.css`, full-screen camera pages (`camera.css`), a worked example, the checklist | [ui-guidelines.md](reference/ui-guidelines.md), G98-G101 |
| Wiki table of contents | [wiki-sidebar.md](reference/wiki-sidebar.md) |

Online docs: https://github.com/caligrafy/caligrafy-quill/wiki

## Project layout (after `caligrafer initialize`)

```
application/                 # YOUR server code - every .php here is auto-required (global namespace, unique class names)
  config/config.php          # app-wide config (keywords(), copyright(), ...)
  config/routing/web.php     # ALL routes; ends with Route::run()
  controllers/               # *Controller.php, any sub-folders (core/Auth, core/AI ship by default)
  models/                    # Model classes + create_db.sql scripts (models/Auth/User.php, models/Auth/create_db.sql)
  services/                  # capabilities: Service classes with #[Capability] (php caligrafer.php service <Folder/Name>); Users/ ships the user capabilities (2.5.0)
  views/                     # .pug templates (default/, Auth/, AI/, Client/app.pug)
public/                      # browser-facing: css, js, images, uploads, client apps public/<appName>/
framework/                   # rarely edited: core/ (framework + Service, Capability, Context...), interfaces/ (Rest, Mcp/, Client), database/; framework/settings/application is the template for application/
.env                         # every key becomes a PHP constant
caligrafer.php, .bin/        # CLI
docker-compose.yaml, docker/ # dev server (8080), phpmyadmin (8077), mysql (3306)
```
`application/` and `.env` do not exist in a fresh clone until `php caligrafer.php initialize` (or `.bin/caligrafer`) runs.

## Conventions that matter

- Every table has primary key `id`. Foreign keys are `<lowercase model name>_id` (model name, not table name).
- One model class per entity, `use Caligrafy\Model;`, one `public $column;` per column, table bound explicitly via `associate()` - there is no implicit table naming.
- **Only capabilities touch models** (2.5.0): pages get their data with `Route::page(..., ['data' => [...]])`, controllers call `$this->call('<capability>')`. Controllers (`use Caligrafy\Controller;`, must have `index()`) are for form posts, several capabilities with conditions, and the session (sign-in); `associate()`/`save()`/`all()` in a controller is legacy MVC.
- Schema is written by hand in SQL (`application/models/<Entity>/create_db.sql`); the framework never migrates (except auto-pivots).
- Many-to-many pivots are created by `belongsToMany()` (typed like the referenced ids, unique constraint names - G10). Name your own FK constraints `fk_<table>_<column>`.
- Model writes save `0`, `''` and `false`; only `null` is skipped (G7). `all()` clauses are raw SQL: use `'bindings'` for values (G8).
- Pages: `Route::page('/books/{id}', 'Books/show', ['data' => ['book' => 'get_book'], 'signedIn' => true])` gives the view `authorized`, `user`, the route variables and the data (G74, G83, G84); prefer it to closures returning `view()`.
- One route file: `application/config/routing/web.php`. Keep the `/` route first (unmatched URLs fall back to route #1, G4) and `Route::run()` last.
- The same route serves HTML and JSON: `view('tpl', $data)` returns JSON automatically for API calls; `api($data)` always returns JSON.

## Canonical recipes

### CRUD resource (model + controller + routes)
```php
// application/models/Book/Book.php
use Caligrafy\Model;
use Caligrafy\Database;
class Book extends Model {
    public $id; public $title; public $author; public $publisher_id; public $created_at;
    public function publisher() { return $this->hasOne('Publisher', 'publishers'); }   // books.publisher_id -> publishers.id
    public function findByTitle($title) {                                               // exact match with a bound query (search() is LIKE)
        $rows = Database::toArray(Database::execute('SELECT * FROM ' . $this->table . ' WHERE title = ?', [$title]));
        return array_map(fn($r) => $this->arrayToModel($r), $rows);
    }
}
```
```php
// application/services/Books/CreateBook.php  (php caligrafer.php service Books/CreateBook --scope books:write)
use Caligrafy\Capability; use Caligrafy\Context; use Caligrafy\Service; use Caligrafy\ServiceError;
#[Capability(name: 'create_book', description: 'Adds a book to the catalog and returns it with its id.', scope: 'books:write')]
class CreateBook extends Service {
    public $rules = ['title' => 'required|max_len,255', 'author' => 'required|max_len,255'];
    public $filters = ['title' => 'trim', 'author' => 'trim'];
    public function handle(array $input, Context $context) { return (new Book('books'))->create($input); }
}
// list_books, get_book (ServiceError::notFound), update_book, delete_book (destructive: true) the same way
```
```php
// application/config/routing/web.php (inside the try, before Route::run())
Route::capability('GET', '/api/books', 'list_books');          // REST
Route::capability('GET', '/api/books/{id}', 'get_book');
Route::capability('POST', '/api/books', 'create_book');
Route::capability('PUT', '/api/books/{id}', 'update_book');
Route::capability('DELETE', '/api/books/{id}', 'delete_book');
Route::page('/books', 'Books/index', ['data' => ['books' => 'list_books']]);      // pages: data from capabilities
Route::page('/books/{id}', 'Books/show', ['data' => ['book' => 'get_book']]);
Route::capabilities('/capabilities'); Route::mcp('/mcp');                          // JS client and AI agents
```
**Only capabilities touch models**: no controller reads or writes `Book`. A thin controller only for a classic form
post, calling the capability (`$this->call('create_book')`, then redirect or re-render with `$e->fields`).
Use the `/caligrafy-quill:new-resource` skill to scaffold this including the SQL script and the capabilities.

### Calling the API
```bash
TOKEN=$(php caligrafer.php token create "my script" --scopes books:read,books:write | grep -o 'cq_[^ ]*')
curl -H "Accept: application/json" -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/books
curl -X POST -H "Accept: application/json" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
     -d '{"title":"Dune","author":"Herbert"}' http://localhost:8080/api/books
```
Browser forms instead need `=csrf()` (and `=methodField('PUT')` for PUT/DELETE) - see G1.

### Pug view with data
```pug
//- application/views/Book/index.pug  -> view('Book/index', ['books' => $books])
extends ../default/master/layoutblocks

block content
    each $book in $books
        p #{$book->title} by #{$book->author}
    form(method='POST', action="{$home}books")
        =csrf()
        input(type='text', name='title')
        input(type='text', name='author')
        input(type='submit', value='Add')
```
Pug variables are PHP variables (`$books`, `$book->title`); `$home` is set by `master/layout.pug`. `layoutblocks` defines the blocks
`headers`, `navigation_default`, `navigation_authorized`, `content`, `footer`, `scripts` (`additional_preload_scripts`, `additional_scripts`).
Pass `'authorized' => authorized()` to show the logged-in navigation, `'install' => true` to invite visitors to install the app
(Install button / iOS Share hint; the layout always links `public/manifest.webmanifest`; offline page with `PWA_SERVICE_WORKER=true`).

### Vue client app with a page route and data routes
```bash
php caligrafer.php app shop          # or bot | facedetect | ml ; 'create' for a Vite SPA
```
```php
Route::get('/shop', function() { return view('Client/app', ['appName' => 'shop', 'app' => 'index']); }); // path ends with the folder name
Route::get('/shop/products', 'ProductController@all');   // data route used by axios
```
In `public/shop/scripts/main.js` send `{ headers: apiHeaders() }` (the session and its `X-CSRF-Token`, 2.7.0+; never the API key, which is off online, G87); read server data from `env` (`env.home`, `env.csrf`, your own view vars). See G15, G16, G27.

### Authentication
Uncomment `Auth::authentication('User', 'users');` and the auth routes in web.php, create the `users` table from `application/models/Auth/create_db.sql` (safe to re-run in current templates; old copies start with `DROP TABLE` - G13), then use `authorized()`, `user()`, `guard('permissions', 1)`, `authorize($user)`, `logout('/')`. Passwords are `password_hash()`ed by `User::hashPassword()`; admin pages need `permissions >= 1` (promote in SQL). Read [09.1-authentication.md](reference/09.1-authentication.md) and G14.

### File uploads
```php
$valid = $this->check($_FILES, ['image' => 'required_file|extension,png;jpg']);
if ($valid === true) { $url = uploadFile($this->request->files('image'), ['png', 'jpg']); }   // '' when refused
```
`uploadFile()` refuses scripts/html/svg whatever the list; `public/uploads/.htaccess` blocks script execution (G34).

## Workflow rules for Claude

1. Before editing, confirm it is a Quill project and whether `application/` and `.env` exist (a project that was never set up: offer `.bin/caligrafer quickstart`, which asks before deleting `.git`) (if not, `php caligrafer.php initialize` or `.bin/caligrafer` is needed - it deletes `.git`, so ask first when the folder is a git repo the user cares about).
2. Read the relevant reference file; prefer the source in `framework/core/` when in doubt.
3. New server code goes in `application/` (never `framework/` unless the user asks to change the framework). Client code goes in `public/`.
4. Use bound `Database::execute()` queries for anything built from user input (G2, G8). Filter then validate (G6).
5. After editing PHP, `php -l <file>`; after changing routes, hit them with curl (JSON + Bearer) or the browser. When you change `framework/` or `framework/settings/application`, run `./qa/run.sh` (G38) and add a test for the change.
6. In a project initialized with an older framework, run `php caligrafer.php upgrade` (dry run, safe) to see which `application/` copies are outdated or customized before assuming the current behavior; read its settings and Bootstrap 3 reports, propose `--keep` for any default page that is really the site's own (an old home page), and apply with `--apply` only when the user agrees (G39). To try a capability you wrote, use `php caligrafer.php call <name> key=value` (2.3.0+).
7. A feature that more than one channel needs (pages and a Vue app, an API, an AI agent) is a capability: a `Service` in `application/services/` with `#[Capability]`, called by `$this->call()`, `Route::capability()`, the JS client and MCP (`/caligrafy-quill:new-capability`). Never put an API key or a token in a page or a bundle (G50): browser clients use the session through the generated client.
8. Every page or client app UI you create follows [ui-guidelines.md](reference/ui-guidelines.md): `extends ../default/master/site` with the classes of `layout.css` (title band, bands, `.cq-field`, `.cq-rows`, `.cq-tiles`), labeled fields, black primary button, no cards, no Bootstrap colors, no page `style` block. Open it in a browser at desktop and phone width before calling it done.
9. To know which framework version a project runs, use `php caligrafer.php version` (or `Caligrafy\Framework::VERSION`; releases are git tags `v<version>`, changes in `CHANGELOG.md`). Releases start at v2.0.0; a project with no `version` command predates it.
10. When something fails at runtime (AI calls returning errors or null, database, payments, mail), run `php caligrafer.php doctor` (read-only; in the container with Docker) before reading code: it checks `.env`, keys (masked), models allowed for the key, retired Groq models, the Agent provider (Ollama reachable, models pulled), the database and the services, with a fix per line.
11. Run SQL scripts with the `/caligrafy-quill:db` skill, start/stop Docker with `/caligrafy-quill:server`, scaffold client apps with `/caligrafy-quill:new-app`.
12. Never print or commit the values of `APP_KEY`, `API_KEY` or other secrets from `.env`; refer to them by name.
13. For a review of Caligrafy code, delegate to the `caligrafy-reviewer` agent.
````

## File: claude-plugin/skills/caligrafy/reference/00-home-overview.md
size: 14604
sha: 3bf9ad261d8cd9527df0b664d0bc926240e50772
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/00-home-overview.md

````text
# Caligrafy Quill overview (wiki Home / README)

> Source: Caligrafy Quill wiki - https://github.com/caligrafy/caligrafy-quill/wiki
> Bundled with the caligrafy-quill Claude plugin. Content preserved from the wiki; formatting cleaned.

# Caligrafy - Describe it. Approve it. Ship it.
![Caligrafy: Describe it. Approve it. Ship it.](https://github.com/caligrafy/caligrafy-quill/blob/master/public/images/resources/describe-approve-ship-banner.png)

**Your idea turned into a complete app.** Describe it to [Claude Code](https://claude.com/claude-code), approve the
plan, and Caligrafy ships it: the database, sign-in, web pages, a phone and tablet app, a REST API and tools for AI
agents, online at `https://<name>.caligrafy.com`, with the code in your own GitHub repository.

## Get started quickly

With Claude Code, three steps take your idea to a complete app online:

| | You | Claude and Caligrafy |
| --- | --- | --- |
| **1. Describe it** | your idea, in a sentence: `/caligrafy-quill:idea A reading list where I rate the books I read` | turn it into a plan: a name, the tables, every feature, who may do what, the pages |
| **2. Approve it** | read the plan, change it, say OK | build every feature once, then check it from a page, REST and an AI agent |
| **3. Ship it** | approve the sign-ins in your browser | create your GitHub repository, deploy it, check it online |

```bash
git clone https://github.com/caligrafy/caligrafy-quill.git myapp
cd myapp
claude
```
```
/caligrafy-quill:idea A reading list: I add books I want to read, mark them as read, and rate them from 1 to 5.
```
You say OK once, at the plan; after that you only approve sign-ins on GitHub's and Caligrafy Cloud's own pages,
which Claude opens for you. It never asks for a password or a key, and asks before spending credits. See
[Quickstart with Claude](https://github.com/caligrafy/caligrafy-quill/wiki/Quickstart-with-Claude).

**Start from a proven prompt.** The [marketplace of caligrafy.com](https://caligrafy.com/marketplace) offers apps that
already shipped, three ways: the prompt that recreates one with Caligrafy, its code, or the app managed for you. And
once yours is online, `/caligrafy-quill:marketplace` offers it there: you earn 25% of every sale. No time to build it?
[Submit your idea](https://caligrafy.com/ideas/new) and Caligrafy builds it for you.

## A complete app: build once, experience everywhere

What makes the app complete is how Caligrafy builds it. A feature (add a task, list the books, place an order) is
written once, as a **capability**: its inputs, its rules, who may use it, and what it does. Caligrafy then serves
that same capability to every way people and machines reach your application:

| Experience | How it uses the capability |
| --- | --- |
| **Web pages** | a page calls it and shows the result |
| **Vue apps** on desktop, tablet and phone | a generated JavaScript function: `await createTask({ title })` |
| **REST API** for other applications | `POST /api/tasks` |
| **AI agents** such as Claude or Cursor | an MCP tool: `create_task` |

The same validation messages, the same permissions and the same audit log, whichever way it is called. You never
write a feature twice, and the experiences never drift apart.

## By hand: the same three steps

Everything Claude does is plain Caligrafy you can do yourself, with one command line, in one project:

| Step | What you do | With |
| --- | --- | --- |
| **1. Describe it** | a running application, then your tables and each feature, written once with its rules | `.bin/caligrafer quickstart`, `create_db.sql` and models, a capability (`php caligrafer.php service`) |
| **2. Approve it** | every experience and every permission, checked: web pages, a Vue app, a phone and tablet app, a REST API, AI agents, sign-in and tokens; tests, a review, a health check | Caligrafy's look, the JS client, `Route::capability`, `Route::mcp`, `scope` and `level`, PHPUnit, the reviewer, `php caligrafer.php doctor` |
| **3. Ship it** | your application at a public address | the production `Dockerfile`, `php caligrafer.php cloud deploy` |

The plugin that ships in every project helps at each step (`/caligrafy-quill:new-capability`,
`/caligrafy-quill:ship`...). The [course](https://github.com/caligrafy/caligrafy-quill-course) follows these three
steps from an empty folder to an application on the internet, so you know what you approve.

## Quickstart by hand: 5 minutes

You need [Git](https://git-scm.com/) and [Docker Desktop](https://www.docker.com/products/docker-desktop/)
(running). Nothing else: PHP, MySQL and Composer run in Docker.

```bash
git clone https://github.com/caligrafy/caligrafy-quill.git myapp
cd myapp
.bin/caligrafer quickstart --example
```
`quickstart` installs the libraries, starts the server, creates the database and the framework tables, and adds a
**Tasks** example (the first start builds the PHP image: a few minutes). When it asks to delete the `.git` folder,
answer `y`: your project starts its own history.

**With Composer, on MAMP, XAMPP, LAMP or Docker.** If you have PHP 8.3+ and [Composer](https://getcomposer.org):
```bash
composer create-project caligrafy/caligrafy-quill myapp
cd myapp
.bin/caligrafer quickstart --example
```
`quickstart` uses your local Apache and MySQL when they are running (create the project in their web folder: it reads
MAMP's own settings, and asks for your MySQL settings with their defaults), Docker otherwise; `--local` or `--docker`
chooses. On Windows: `php caligrafer.php quickstart --example`. See
[Quickstart with Composer](https://github.com/caligrafy/caligrafy-quill/wiki/Quickstart#composer).

**1. Use it on the web.** Open **http://localhost:8080/tasks**, add a task, tick it, rename it.

**2. Use it from another application.** The same tasks, over REST:
```bash
curl -H "Accept: application/json" http://localhost:8080/api/tasks
```

**3. Use it from an AI agent.** Create a token and give it to Claude Code (or Cursor, below):
```bash
docker compose exec dev-box php caligrafer.php token create "my agent" --scopes "*"
claude mcp add --transport http myapp http://localhost:8080/mcp --header "Authorization: Bearer <the token>"
```
Ask Claude "add a task to buy milk": it calls `create_task`, and the task appears on the web page within seconds.
In Cursor, add to `.cursor/mcp.json`:
```json
{ "mcpServers": { "myapp": { "url": "http://localhost:8080/mcp", "headers": { "Authorization": "Bearer <the token>" } } } }
```

**4. Ship it.** Push the project to a GitHub repository (`git init`, commit, push), then:
```bash
docker compose exec dev-box php caligrafer.php cloud login                 # open the address it prints, approve the code
docker compose exec dev-box php caligrafer.php cloud create myapp --repo https://github.com/<you>/myapp
docker compose exec dev-box php caligrafer.php cloud deploy myapp          # https://myapp.caligrafy.com
```
`cloud login` opens [deploy.caligrafy.com](https://deploy.caligrafy.com), where you sign in (or sign up) and approve.
Every new app is free for its first 30 minutes online. With Claude Code, `/caligrafy-quill:ship myapp` does all of
this for you, the GitHub repository included. The Tasks example is open to everyone, online too: see
[Putting it online](https://github.com/caligrafy/caligrafy-quill/wiki/Putting-it-Online) before you share the address.

That is the whole idea: the Tasks feature lives in five small files in `application/services/Tasks/`, and every
experience above uses them.

## Your first capability

```bash
docker compose exec dev-box php caligrafer.php service Books/CreateBook --scope books:write
```
This creates `application/services/Books/CreateBook.php`. Fill in its inputs and its work:
```php
#[Capability(name: 'create_book', description: 'Adds a book to the catalog and returns it.', scope: 'books:write')]
class CreateBook extends Service
{
    public $rules = array('title' => 'required|max_len,255', 'author' => 'required|max_len,255');

    public function handle(array $input, Context $context)
    {
        return (new Book('books'))->create($input);
    }
}
```
AI agents see it at once as the `create_book` tool. Give it a REST route in
`application/config/routing/web.php` with `Route::capability('POST', '/books', 'create_book');`, and a JavaScript
function for your pages with `php caligrafer.php client`. The tutorial
[Your first capability](https://github.com/caligrafy/caligrafy-quill/wiki/Your-First-Capability) builds a complete
feature step by step, page included.

## What comes in the box

+ **Every experience:** server-rendered pages (Pug), Vue apps and a Vite single page application, installable mobile
  web apps, REST, and an MCP server for AI agents.
+ **One rule:** only capabilities touch your data. Pages get their data from capabilities in one line
  (`Route::page('/books/{id}', 'Books/show', ['data' => ['book' => 'get_book']])`), so a page, the API and an agent
  never follow different rules.
+ **One look, or yours:** Caligrafy's look on every page: a light top bar with the Caligrafy mark, title bands, forms,
  rows and tiles, Caligrafy black for the main action, full-screen camera apps, the same on every device. Or pick one of 85 [design styles](https://github.com/caligrafy/caligrafy-quill/wiki/Design-Styles)
  (`DESIGN_STYLE` in `.env`, or your own `DESIGN.md`) and let `/caligrafy-quill:design` restyle every page.
+ **Secure by design:** sign-in, passkeys and face login for people; scoped, revocable tokens for programs and
  agents; CSRF protection; an audit log of every change; server files never served.
+ **Batteries included:** card, ACH and crypto payments, OpenAI and open-model AI (Llama on Groq, OpenRouter,
  Ollama...), chatbots, face detection and machine learning in the browser, search and social metadata.
+ **Made for AI-assisted development:** a [Claude Code plugin](https://github.com/caligrafy/caligrafy-quill/wiki/10.-Claude-Code-Plugin)
  that knows the framework, builds features the Caligrafy way or a whole application from your idea, and puts it
  online, and `AGENTS.md` for other coding agents.
+ **Online in a few commands:** a production `Dockerfile` in every project, the database prepared at every start,
  and [Caligrafy Cloud](https://github.com/caligrafy/caligrafy-quill/wiki/Putting-it-Online#cloud):
  `php caligrafer.php cloud deploy` puts it at `https://<name>.caligrafy.com`, every new app free for its first 30
  minutes, and `/caligrafy-quill:ship` does it all for you, from the GitHub repository to the check online.
+ **Sell what you built:** offer an app online on the [marketplace of caligrafy.com](https://github.com/caligrafy/caligrafy-quill/wiki/Putting-it-Online#marketplace):
  Caligrafy sells a prompt that recreates it and you earn 25% of every sale. `/caligrafy-quill:marketplace` reads
  your code, screenshots its key features and submits the listing (`php caligrafer.php cloud submit`).
+ **Tools:** `php caligrafer.php doctor` checks your setup and services, `call` tries a capability in one line,
  `update` moves a project to the latest release in one command (keeping what you changed), `upgrade` updates its
  `application/` templates and tells you what to check, `version` tells you which release you run.

## Where to go next

| You want to | Read |
| --- | --- |
| Get a feel for it | [Quickstart with Claude](https://github.com/caligrafy/caligrafy-quill/wiki/Quickstart-with-Claude) or the [Quickstart](https://github.com/caligrafy/caligrafy-quill/wiki/Quickstart), then [How Caligrafy works](https://github.com/caligrafy/caligrafy-quill/wiki/How-Caligrafy-Works) |
| Build a feature | [Your first capability](https://github.com/caligrafy/caligrafy-quill/wiki/Your-First-Capability), then [Capabilities in depth](https://github.com/caligrafy/caligrafy-quill/wiki/11.-Build-Once:-Services,-Capabilities-and-MCP) |
| Build a web page | [Views and Caligrafy's look](https://github.com/caligrafy/caligrafy-quill/wiki/7.-Views#pagecomponents) |
| Build a Vue app or a single page application | [Vue as a library](https://github.com/caligrafy/caligrafy-quill/wiki/9.7.0-Setting-up-Vue.js-as-a-library), [the single page application](https://github.com/caligrafy/caligrafy-quill/wiki/9.8.1---Setting-up-the-Vue-application) |
| Let AI agents use your application | [MCP](https://github.com/caligrafy/caligrafy-quill/wiki/11.-Build-Once:-Services,-Capabilities-and-MCP#mcp) |
| Store data | [Models](https://github.com/caligrafy/caligrafy-quill/wiki/4.-Models) and [Relationships](https://github.com/caligrafy/caligrafy-quill/wiki/5.-Relationships) |
| Let people sign in | [Sign-in, users and tokens](https://github.com/caligrafy/caligrafy-quill/wiki/9.1-Authentication) |
| Take payments, add AI, face login... | the **Built-in features** of the sidebar |
| Put it online | [Putting it online](https://github.com/caligrafy/caligrafy-quill/wiki/Putting-it-Online): Caligrafy Cloud or your own server |
| Sell your app | [Selling your app on the marketplace](https://github.com/caligrafy/caligrafy-quill/wiki/Putting-it-Online#marketplace): a prompt that recreates it, 25% of every sale |
| Build with Claude | [Claude Code plugin](https://github.com/caligrafy/caligrafy-quill/wiki/10.-Claude-Code-Plugin) |
| Learn step by step, from zero | [The Caligrafy course](https://github.com/caligrafy/caligrafy-quill-course) |
| Use the command line (set up, create, tokens, check, upgrade) | [Caligrafer: every command](https://github.com/caligrafy/caligrafy-quill/wiki/Caligrafer) |
| Install another way, configure, upgrade | [Installation and configuration](https://github.com/caligrafy/caligrafy-quill/wiki/1.-Getting-Started), [Security and upgrading](https://github.com/caligrafy/caligrafy-quill/wiki/9.5-Security-and-Upgrading) |

## Community and support

+ **GitHub:** [issues](https://github.com/caligrafy/caligrafy-quill/issues) for bugs and feature requests.
+ **Slack:** [join the Caligrafy community](https://join.slack.com/t/caligrafy/shared_invite/enQtNzI4MDY2OTA4MTgzLTI2NDc2NTVmMDNlMWQ5YWYxN2RjZTkwZjdiNjM5ZTg3NjQ2YWYyMzRmZDgzNWE0Nzc4YjQyODM2NDNkNjQ2OTU) to get help and share what you build.
+ **Facebook:** the [Caligrafy group](https://www.facebook.com/groups/caligrafy/).
+ **Videos:** the [Caligrafy channel](https://www.youtube.com/playlist?list=PLsJG81fLk3_VWmXl9DnEldYY9ei4jTCjA).

If Caligrafy helps you, [sponsor it on GitHub](https://github.com/sponsors/DoryAzar) or share
[caligrafy.com](https://caligrafy.com).

Caligrafy is released under the [MIT License](https://github.com/caligrafy/caligrafy-quill/blob/master/LICENSE).
````

## File: claude-plugin/skills/caligrafy/reference/01-getting-started.md
size: 26884
sha: e3faaa3bcb25a9f7fed72236a1fc9a894972ef52
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/01-getting-started.md

````text
# 1. Getting Started

> Source: Caligrafy Quill wiki - https://github.com/caligrafy/caligrafy-quill/wiki/1.-Getting-Started
> Bundled with the caligrafy-quill Claude plugin. Content preserved from the wiki; formatting cleaned.

> **Verified against framework source (see gotchas.md):**
> - Under Docker, `.env` overrides docker-compose environment (Dotenv `createUnsafeMutable`): set `DB_HOST=mysql` and `APP_ROOT=` in `.env`. Ports 8080/8077/3306 may clash with other projects (gotchas G11/G12).
> - `caligrafer initialize` keeps the keys of a signed project but still deletes `.git` (G25). `app` = Vue as a library, `create` = Vite SPA.

> **New to Caligrafy?** Start with the [Quickstart](https://github.com/caligrafy/caligrafy-quill/wiki/Quickstart): with Git and Docker, one command (`.bin/caligrafer quickstart --example`) sets everything up, and you use one feature from a web page, over REST and from an AI agent in 5 minutes. This page is the reference for every other way to install, for the configuration and for the Caligrafer commands.

> ## Requirements
> + PHP 8.3 or higher (8.5 recommended)
> + MySQL 8.4 (or another database supported by PDO)
> + A Apache/MySql/PHP server already installed locally OR Docker
> + Git and Composer
> + curl, mbstring, openssl, sodium, gd, bcmath, intl, pdo_mysql, and the Apache rewrite and headers modules must be enabled in your servers
> + Node.js 20.19+ or 22.12+ for the Vue Single Page Application template

> #### (video) [Introduction to Caligrafy](https://youtu.be/zTqbWlTPrNQ)
>
> In this video we introduce you to the Caligrafy framework and to the different components that are used in it.

> ## Join the Caligrafy Community
> There are several ways for you to interact with the Caligrafy community
> + **github:** You can always use github to stay up to date with the roadmap of Caligrafy, to post issues and to track when feature requests and issues are done
> + **slack:** Joining our slack group is a great way to exchange with other members of the community, to get help on using the framework and to discuss any issues or features.
[Join our slack community](https://join.slack.com/t/caligrafy/shared_invite/enQtNzI4MDY2OTA4MTgzLTI2NDc2NTVmMDNlMWQ5YWYxN2RjZTkwZjdiNjM5ZTg3NjQ2YWYyMzRmZDgzNWE0Nzc4YjQyODM2NDNkNjQ2OTU)
> + **facebook Caligrafy Group:** Joining our Caligrafy group on facebook gives you more ways to interact with the community and to share success stories. [Join our facebook group](https://www.facebook.com/groups/caligrafy/)

## Installation

### Quickstart (recommended)

With [Git](https://git-scm.com/) and [Docker Desktop](https://www.docker.com/products/docker-desktop/):
```bash
git clone https://github.com/caligrafy/caligrafy-quill.git myapp
cd myapp
.bin/caligrafer quickstart          # add --example for the Tasks example
```
It installs the libraries (Composer in Docker), starts the server, initializes the project, points `.env` at the Docker database, creates the database and the framework tables, and adds the `/capabilities` and `/mcp` routes. Every step can be run again. Details and troubleshooting: [Quickstart](https://github.com/caligrafy/caligrafy-quill/wiki/Quickstart). The options below are for your own Apache, MySQL and PHP, or for full control.

### Quick Installation with Composer (MAMP, XAMPP, LAMP or Docker)

> #### (video) [Installing Caligrafy](https://youtu.be/21Lazd38FfQ)
>
> In this video we show you how to install Caligrafy on a local Apache/MySQL/PHP server.

```bash
composer create-project caligrafy/caligrafy-quill myapp      # in the web folder of your local server
cd myapp
.bin/caligrafer quickstart                                   # add --example for the Tasks example
```
This installs the latest release (see the [changelog](https://github.com/caligrafy/caligrafy-quill/blob/master/CHANGELOG.md); add `-s dev` for the unreleased `master` branch). `quickstart` uses your running MAMP, XAMPP or LAMP, else Docker (`--local` or `--docker` chooses; on Windows run `php caligrafer.php quickstart`): it initializes the project, gives it its address (`APP_ROOT`, from its place in the web folder; MAMP's own settings are read), asks for your MySQL settings with the server's defaults, and creates the database, the framework tables and the `/capabilities` and `/mcp` routes. Every step can be run again. Details: [Quickstart with Composer](https://github.com/caligrafy/caligrafy-quill/wiki/Quickstart#composer).

**Testing the Installation**
+ Open the address quickstart prints, for example `http://localhost:8888/myapp` with a default MAMP, or `http://localhost/myapp`.

### Using Docker

> #### (video) [Installing Caligrafy using Docker](https://youtu.be/33TKktyxto4)
>
> In this video we show you how Caligrafy can be installed and run without setting up any development environment.

This method requires Docker to be installed on your local machine. With Docker, you no longer need to go through the hassle of having an Apache server installed locally to run PHP and MySQL. The stack runs PHP 8.5 with Apache (`php:8.5-apache`), MySQL 8.4 LTS and phpMyAdmin 5.2.3.

+ If you have PHP and Composer installed on your machine, check the `Quick installation`.

+ If you don't have Composer then pull the code from github (You can either clone the repo or download the zip file)

+ Make sure Docker is installed and is running on your machine

+ Run the following code from the Caligrafy root folder to start the Caligrafy server

    ```bash
    .bin/caligrafer server start
    ``` 

    + **With PHP and Composer on your machine**, `.bin/caligrafer` installs the dependencies and initializes the project when needed, then starts the `dev-box` container with your folder mounted into it: your changes are live. Check that `.env` has `APP_ROOT=` (blank). (`php caligrafer.php server start` alone does nothing: the command is meant to be called through `.bin/caligrafer`. Without it, run `docker compose up -d dev-box`.)
    + **Without PHP**, `.bin/caligrafer` runs `.bin/server`, which starts the `prod-box` container: the code is copied into the image, `.env` is created from `framework/settings/.env.docker` (`APP_ROOT=caligrafy-quill`) and the image is rebuilt when files change. The site is then at `http://localhost:8080/caligrafy-quill`.

+ **How long it takes.** The first start builds the server image: it downloads a few system libraries and compiles the PHP extensions Caligrafy needs (gd, intl, zip, pdo_mysql, bcmath), which takes about 5 minutes. Every later start reuses the image and takes a few seconds; a new project on the same machine reuses Docker's build cache.
+ Run `server rebuild` to rebuild the image on purpose (after a Dockerfile change, or to update its system packages); `server start` never rebuilds an image that already exists.
+ Run `server stop` instead of `server start` to stop the server at all time

**Testing the Installation**
+ Once started, you can access the website locally via `http://localhost:8080` (`http://localhost:8080/caligrafy-quill` with `prod-box`)

**Testing the Database provided in Docker**
+ In both commands, a `phpmyadmin` instance  has been added and can be accessible at `http://localhost:8077/`
+ This is a web client for MySQL. It will allow you easy access to your Database.
     - Username: `root`
     - Password: `root`

### Manual Installation
+ Pull the code from github (You can either clone the repo or download the zip file)
+ It is recommended to place the repo at the Server Document Root level
+ Go to the downloaded repo and create a .env file by copying the example `cp .env.example .env`
+ Create an `APP_KEY` and an `API_KEY` in the .env file. You can use Caligrafer to generate API keys for you by running `php caligrafer.php generatekeys` and adding the generated keys to the .env file
+ Add the following to the .env file if not present: `APP_ROOT=<caligrafy root folder. default: caligrafy-quill>`. If caligrafy is not installed at the Server Document Root level, refer to the [Different Root Folder instructions](https://github.com/caligrafy/caligrafy-quill/wiki/1.-Getting-Started#different-root-folder).
+ Change the other values in .env file to match your local or production server settings
+ Run `composer install` to get all the dependencies needed
+ Initialize the application folder by running the following `cp -r framework/settings/application ./application`. Unlike `initialize`, this copy writes no `application/.caligrafy-manifest.json`: `php caligrafer.php upgrade` then recognizes your untouched files from the template history instead
+ Make the folder `/public/uploads/` writable if you intend to allow uploads in your application. You will need to run the command `chmod -R 777 public/uploads` from the Caligrafy root
+ You are good to go!

### Testing the Installation
+ You can test if the framework is working by visiting `http://localhost:<server port, default 80>/<caligrafy root folder. default: caligrafy-quill>` in the browser. 

### Different Root Folder

#### The default root folder is `caligrafy` or `caligrafy-quill` (depending on what version of caligrafy you got). To change the name of this root folder:
+ Rename the folder `caligrafy` to `your-app-name` OR `git clone <caligrafy git repository> your-app-name`
+ Change the `APP_ROOT` entry in the `.env` file to `your-app-name`

#### Placing the Root Folder in subfolders within the Server Document Root
If you decide to place the Caligrafy repo in subfolders within the Server Document Root, you will need to modify the .env file accordingly regardless whether you picked the automatic or manual installation:
+ Change/Add `APP_ROOT` entry in the `.env` file to the `full path leading to the caligrafy root folder from server document root`

### Caligrafer
Caligrafer is the command line tool of Caligrafy: `php caligrafer.php <command>` (with Docker: `docker compose exec dev-box php caligrafer.php <command>`). **Every command is described on one page: [Caligrafer](https://github.com/caligrafy/caligrafy-quill/wiki/Caligrafer).** The ones you use most:

| Command | What it does |
| --- | --- |
| `quickstart [--example]` | sets up a project in one step ([details](https://github.com/caligrafy/caligrafy-quill/wiki/Caligrafer#quickstart)) |
| `initialize` | prepares a fresh copy of the framework; asks before deleting `.git` (`--yes`: without asking) ([details](https://github.com/caligrafy/caligrafy-quill/wiki/Caligrafer#initialize)) |
| `service <Name>` | creates a capability ([details](https://github.com/caligrafy/caligrafy-quill/wiki/Caligrafer#service)) |
| `client` | writes the JavaScript client of the capabilities ([details](https://github.com/caligrafy/caligrafy-quill/wiki/Caligrafer#client)) |
| `token create <name> --scopes ...` | a token for a program or an AI agent ([details](https://github.com/caligrafy/caligrafy-quill/wiki/Caligrafer#token)) |
| `app`, `create`, `facedetect`, `ml`, `bot` | creates a client app in `public/` ([details](https://github.com/caligrafy/caligrafy-quill/wiki/Caligrafer#apps)) |
| `sql <file.sql>` | runs a SQL script on the database ([details](https://github.com/caligrafy/caligrafy-quill/wiki/Caligrafer#sql)) |
| `doctor` | checks the project and its services ([details](https://github.com/caligrafy/caligrafy-quill/wiki/Caligrafer#doctor)) |
| `upgrade` | updates a project's templates after a framework update ([details](https://github.com/caligrafy/caligrafy-quill/wiki/Caligrafer#upgrade)) |

+ The version of a project: `php caligrafer.php version` ([details](https://github.com/caligrafy/caligrafy-quill/wiki/Caligrafer#version)).
+ Capabilities, tokens, MCP and the audit log: `service`, `capabilities`, `client`, `platform install`, `token`, `mcp`, `audit` ([details](https://github.com/caligrafy/caligrafy-quill/wiki/Caligrafer#service)).
+ Something does not work: `php caligrafer.php doctor` checks the project, the database and every service in `.env`, with a fix for each problem ([details](https://github.com/caligrafy/caligrafy-quill/wiki/Caligrafer#doctor)).

### Virtual Hosts
Creating a virtual host is optional. Caligrafy does not need the creation of virtual hosts to operate. However, any virtual host used will need to ensure to `AllowOverride All`, otherwise Caligrafy may not function properly.
To create a virtual host to point to the framework:
+ In your local machine, locate the `httpd-vhosts.conf` of your apache server. Usually it is located in `/apache2/extra`
+ In a production server, locate the `000-default.conf` that is typically in `/etc/apache2/sites-available`
+ Add a virtual host:
```
<VirtualHost *:80>
    ServerName <example.loc>
    DocumentRoot "<path-to-server-root>/caligrafy"
    <Directory "<path-to-server-root>/caligrafy">
       Options FollowSymLinks
       AllowOverride All
       Require all granted
    </Directory>
</VirtualHost>
```
+ Restart your apache server
+ Add a virtual host in your local machine by opening `/etc/hosts` file and adding the following:
```
127.0.0.1       example.loc

```

### Protecting your server files
Only the `index.php` front controller and the `public` folder are meant to be reached from a browser. The `.htaccess` file at the root of Caligrafy denies direct access to:
+ dotfiles such as `.env`, `.git` and `.claude`
+ the server-side folders `application`, `framework`, `vendor`, `tests`, `qa`, `docker` and `claude-plugin`
+ tooling files such as `caligrafer.php`, `composer.json`, `composer.lock` and `docker-compose.yaml` and `package.json`, the `archives` and `node_modules` folders, and any `.sql`, `.log`, `.bak`, `.ini`, `.sh` or `.env` file

A second `.htaccess` in `public/uploads` prevents any script that ends up in the uploads folder from being executed or downloaded.

Keep these files when you deploy. They use `mod_rewrite`, `mod_headers` (CORS, cookies, the service worker scope) and `mod_mime` (the web app manifest type), so your server needs these modules enabled and `AllowOverride FileInfo` (or `All`): without `mod_headers`, Apache answers `500`. The rules are case-insensitive, which matters on macOS and Windows file systems. You can check your server with `curl -I http://<your-host>/.env`: the answer must be `403` or `404`.

If you serve Caligrafy with **nginx**, `.htaccess` files are ignored and you need equivalent rules, for example:
```
location ~ /\.(?!well-known) { deny all; }
location ~ ^/(application|framework|vendor|tests|qa|docker|claude-plugin|archives|node_modules|backups)/ { deny all; }
location ~ ^/(caligrafer\.php|composer\.(json|lock)|docker-compose\.ya?ml|package(-lock)?\.json)$ { deny all; }
location ~* \.(sql|log|bak|ini|sh|env)$ { deny all; }
location = /public/sw.js { add_header Service-Worker-Allowed "/"; }
types { application/manifest+json webmanifest; }
location ~* ^/public/uploads/.*\.(php\d*|phtml|phar|cgi|pl|py|sh)$ { deny all; }
location / { try_files $uri $uri/ /index.php?uri=$uri&$args; }
```

### Caligrafy Namespace
The Caligrafy framework is all in the namespace `Caligrafy`. Every class that you create will need to call the appropriate classes from the namespace. 

- If you are creating a Controller:
```php
use Caligrafy\Controller;

class MyController extends Controller {
// your code goes here
}
```
- If you are creating a Model:
```php
use Caligrafy\Model;

class MyModel extends Model {
// your code goes here
}
```

### Build your own local Apache/MySQL/PHP server to run Caligrafy

If you would like to build your own server, you will need to go through the following setup items:

1. Prepare your terminal and Git: [lesson 00 of the Caligrafy course](https://github.com/caligrafy/caligrafy-quill-course/wiki/00-Introduction-and-Tools) installs and checks them (macOS, Linux, Windows with WSL)

2. Prepare your development environment: a local Apache, MySQL and PHP 8.3+ (MAMP, LAMP, XAMPP or your own install) with the extensions listed under Requirements at the top of this page

3. Download a code editor (for example [Visual Studio Code](https://code.visualstudio.com/) or [Sublime Text](https://www.sublimetext.com/))

## MVC Framework

This framework is based on a Model-View-Controller architecture that separates the concerns between back-end and front-end.
The framework relies on the following structures:
+ **Database**: The framework supports all the SQL databases that PDO supports. We recommend using MySql given its tight integration with PHP
+ **Model**: The model is the back-end that contains all the data logic. It is the layer that interfaces with the storage and manages the data, whether it be a database, a JSON object. Caligrafy uses databases for data storage and therefore the model becomes the object representation of the main objects/entities of an application. In other words, every table in your database that represents an entity should have an equivalent model created in the framework
+ **Controller**: The controller in an MVC architecture is the brain of the application that controls how the information is displayed. It is the logic layer that is responsible for interfacing with the model and rendering the data that will be displayed in the View
+ **View**: The view is the front-end part that is responsible for displaying a user interface with the data that the controller passes on to it

### Build once, experience everywhere

Caligrafy adds two layers on top of MVC so that a feature is built once and experienced everywhere:

| Layer | What it holds | Where |
| --- | --- | --- |
| **Capabilities** | the features of your application, written once: inputs and their rules, who may use them, the work, the result | `application/services/` (a `Service` with `#[Capability]`) |
| **Experiences** | every way to reach a capability, each shaped for its audience | Pug pages, Vue apps and the SPA on desktop, tablet and phone, REST for other applications, MCP for AI agents |

Models stay the data layer under the capabilities; controllers and views become one experience among others. You can start with plain MVC and move a feature to a capability as soon as a second experience needs it. Learn more in [11. Build Once](https://github.com/caligrafy/caligrafy-quill/wiki/11.-Build-Once:-Services,-Capabilities-and-MCP).

## Framework Fundamentals

This framework relies on several foundational concepts that are worth detailing. A good understanding of those fundamental concepts gets the developers up and running more efficiently. We will use an online catalogue of books (like Amazon) as an example throughout this documentation.

+ **Routes**: Routing is a key element to any framework. It uses the attributes of a user request to inform the application on what action needs to be taken on what object. That process of deconstructing requests and translating them into a call to engage controllers is called Routing. For example, if you are accessing `http://example.com/books` from your browser, this is sending a GET request on a BOOK object. In other words, this is requesting to view a list of books. Routing is responsible for redirecting such a URL to the controller in the application that is/will be responsible for viewing the list of all books.

+ **Controllers**: Controllers are a logical grouping of actions that can be taken on an object. For every object or entity, there should be one controller consisting of a group of actions that can be performed on that object (Create, Read, Update, Delete, etc.). Routing allows redirecting to a specific action in the controller.

+ **Views**: Caligrafy has two different ways of building views. One for rapid development that uses a server-side template engine to render HTML pages. The other one uses Vue.js which is a more sophisticated client-side framework that is suitable for building more delightful user experiences.You could learn more about Pug (or Phug the PHP equivalent) [here](https://www.phug-lang.com/)

+ **API & JSON**: This framework relies on REST API fundamentals. Every route that is created could either be accessed from a browser or from any application capable of making requests. The Caligrafy View is intelligent to know whether to render a graphical user interface or a response in a JSON format.

+ **Capabilities**: a capability is one feature of the application (add a book, list the books) written once as a service: it declares its inputs with validation rules, the scope and permission level it needs, does the work and returns data. Every experience calls the same capability, so the rules never drift between the web, the apps, the API and the agents. Learn more [here](https://github.com/caligrafy/caligrafy-quill/wiki/11.-Build-Once:-Services,-Capabilities-and-MCP#service).

+ **Experiences**: web pages, Vue apps and the single page application on any device, REST clients and AI agents (MCP) are all experiences of the same capabilities. Each one is shaped for its audience (a dense dashboard, a touch-first tablet view, a tool for an agent) without re-implementing the logic.

+ **Identity**: people sign in (sessions, passkeys, face login); programs and AI agents use personal access tokens with scopes, which can expire and be revoked. Every capability call, and every refusal, goes to the audit log.

+ **Models**: In this framework, models are meant to be an object representation of all objects/entities of an application. For example, in an online bookstore example, books need to be represented by a Book model. Every model has a table associated with it in the database. Unlike other frameworks, this framework does not implicitly associate a model with a table (like Laravel for example). Every Model needs to be explicitly associated with a table. That gives the flexibility of separating terminology concerns between database tables and their object representations in the framework. However, all the required table columns that have no default values need to be represented in the model. Pivot tables (or many-to-many database tables) don't need to be created in the database nor represented in the Model. This framework creates these tables from the moment a many-to-many relationship is defined between Models (more on this later).

+ **Relationships**: This framework supports one-to-one, one-to-many and many-to-many relationships between models in both directions. Each relationship is defined as part of the Model. For example, if books and publishers are two models in our example and one book has one publisher then the one-to-one relationship needs to be defined in the Book Model (hasOne relationship). Similarly, the database needs to have a foreign key in the Book table that references the id in the Publisher table. For this to happen smoothly, a naming convention needs to be followed.

+ **Naming conventions**: This framework does not put constraints on the naming conventions as much as other frameworks. The only naming convention is around the table keys in the database.
    1. id: Every table must have `id` as its primary key no matter what the table name is. It is therefore recommended to avoid naming primary keys `book_id` or `bookId`. The primary key must always be `id` and cannot be overridden.
    2. Foreign keys: the foreign keys in the database should be of the format `modelname_id` (model name in lower case). Notice that the foreign key takes the name of the Model and NOT the name of the table in the database. The foreign keys name could be overridden if need be. A foreign key column must have the same type as the `id` it references (for example `INT UNSIGNED` for an `INT UNSIGNED` id), and every foreign key constraint needs a name that is unique in the database (for example `fk_books_publisher`).
    3. Pivot table naming: the framework takes care of creating the pivot tables for your convenience. The pivot table is named `<table1>_<table2>` after the tables of the model that calls the many-to-many relationship (belongsToMany) first and of the joined model. If a `<table2>_<table1>` table already exists, it is reused.

+ **Validation**: Input validation is key to any application. This framework integrates a third-party GUMP - a very solid library - to validate all types of inputs. The validation module is capable to do 2 things: 1) Validating inputs of all types based on easy-to-define  rules 2) Provide easy ways to filter data and format it based easy-to-define rules. You could learn more about GUMP [here](https://github.com/Wixel/GUMP)

## File Structure

Understanding the framework file structure is key to understanding how to plan your development project.
There are 5 main folders in the structure that you should be aware of:
1. **`/application`**: 
    This is where most of your application logic should go. This folder is where you should create the routes, models, views and controllers. You can create any classes and organize them freely in this folder without worrying about requiring them or including them: every PHP file of `application` (up to 5 folder levels deep) is loaded on each request, so two classes cannot have the same fully qualified name. 

    This folder has 5 subfolders: models, views, controllers, config (where the routes are defined) and services, where your capabilities live: features written once and served to every experience. 

    > When your logic becomes too complex to hold in a `Controller`, you can always create new classes to organize them without having to require or include them in the classes where you intend to use them.
2. **`/framework`**: This is the underlying code for the framework. For novice programmers, you will very rarely need to change anything in this folder. However, if you need to change the way the framework operates, then this is where it can be done. Inside it, `core/` holds the framework and what a capability is (`Service`, `Capability`, `Context`...), and `interfaces/` how the world reaches a capability: REST, MCP for AI agents and the JS client of the Vue apps.
3. **`/public`**: This is where your application client-side code should go. All your scripts, stylesheets and any other resource that needs to be publicly accessible will reside in this folder. For example, any javascript or css that you need to reference from your Views (residing in application) will need to be in the public folder. Similarly, if your application supports uploading files, all uploaded files will live in the `/public/uploads` folder.
4. **`/tests`**: every PHP file of this folder is loaded on each request, like `application`: use it for helpers that your application needs, not for PHPUnit tests (the framework's own unit tests live in `qa/unit`, see [Testing Caligrafy](https://github.com/caligrafy/caligrafy-quill/wiki/Testing-Caligrafy)).
5. **`/vendor`**: The vendor folder contains all the third-party libraries that this framework depends on.

> [Next Section: Learn about Routes](https://github.com/caligrafy/caligrafy-quill/wiki/2.-Routing)
````

## File: claude-plugin/skills/caligrafy/reference/02-routing.md
size: 11399
sha: 54ae9d046c8dddeffa883702482cfb0f6d2a11aa
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/02-routing.md

````text
# 2. Routing

> Source: Caligrafy Quill wiki - https://github.com/caligrafy/caligrafy-quill/wiki/2.-Routing
> Bundled with the caligrafy-quill Claude plugin. Content preserved from the wiki; formatting cleaned.

> **Verified against framework source (see gotchas.md):**
> - Unmatched URLs do not 404: they run the FIRST route registered in web.php (gotchas G4).

> **Where this fits.** Routes connect URLs to what answers them. For a feature written as a capability, one line is enough: `Route::capability('POST', '/books', 'create_book')` for REST, and `Route::capabilities()` and `Route::mcp()` (added by the [Quickstart](https://github.com/caligrafy/caligrafy-quill/wiki/Quickstart)) for the Vue apps and AI agents: see [Capability Routes](#capabilityroutes). The rest of this page covers page routes and controller routes.

The very first thing that you need to do when building your application is structuring your URIs in a flexible and scalable way that is agnostic of how your application files are structured. This framework does it all for you. What you need to focus your effort on is planning where the URIs should go.

## Defining Routes
A route file is provided for you `/application/config/routing/web.php`. This is where your routes should be defined. There are 4 route methods available
```php
Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);
Route::delete($uri, $callback);
```
Keep the following rules in mind when you define routes:
+ A request matches a route when the HTTP method is the same and the path has the same number of segments, each fixed segment being identical. `{variables}` match any single segment.
+ The routes are evaluated in the order they are defined and the first match wins.
+ When no route matches, the answer is **404 Page Not Found** (`[ERROR 1005]`, or `{"error": ...}` to an API call); when the address exists but not with that HTTP method, **405** with an `Allow` header listing the methods it takes. `HEAD` is answered like `GET`. Before 2.16.0 an unmatched URL ran the first route of the file instead.
+ `Route::run();` must always be the last statement inside the `try` block of the file.

Your first reaction might be that HTML `forms` do not support `PUT` and `DELETE`. True that and the framework is capable of doing that work for you. You can check [here](https://github.com/caligrafy/caligrafy-quill/wiki/2.-Routing#htmlformmethods) how it is done.

## "Hello World" Route
The most basic route provides a simple way to execute any method (through Closure) upon the definition of the route. `application/config/routing/web.php` already has one `try` block, and the template already defines this route: add yours inside the `try`, before `Route::run();`.
```php
try {
    // ...
    Route::get('helloworld', function() {
            echo 'Hello World';
    });

    Route::run();
} catch (Exception $e) {
    Errors::handle($e);   // HTTP status code (404, 401, 403, 500...) with the error text, or JSON for API calls
}
```
You should be able to access it by navigating to `http://your-app/helloworld` in your browser: `http://localhost/caligrafy-quill/helloworld` on a local server where the folder is `caligrafy-quill`, or `http://localhost:8080/helloworld` with Docker.

> #### (video) [Basic Routing](https://www.youtube.com/watch?v=VUxL2lVgs4E&list=PLsJG81fLk3_UNfTuqjCbcz1S_xbram35e)
>
> In this tutorial, we test if the Caligrafy framework has been installed properly, we explain how basic routing can be achieved and we create a couple of basic routes.

## Routing with parameters
It is not unusual to want to capture variables from the URI to act on them. For example if you want to access all the information on a specific book, you would want the URI to be `http://your-app/books/1` where `1` is the id of the book that you want.

In order to do that with this framework you need to use `{variablename}` syntax:
```php
Route::get('books/{id}', function() {
   // any code would go here
});
```
By doing so, any value that you put after `books/` will be captured and you will be able to use it in your code. So if you navigate to `http://your-app/books/1`, your code will be able to capture 1. Similarly, if you replace the one with another value, your code will be able to capture that value. Later you will learn how you can fetch this value in your code.

## Page Routes

Since 2.5.0 a **page** has its own route method. `Route::page` renders a view through the framework's page controller, so every page is built the same way:
```php
Route::page('/', 'default/index');                                             // a page
Route::page('/books', 'Books/index');                                          // its data comes from the JS client in the browser
Route::page('/books/{id}', 'Books/show', ['data' => ['book' => 'get_book'], 'notFound' => 'Books/missing']);  // its data comes from a capability
Route::page('/account', 'Account/index', ['signedIn' => true]);                // signed-in people only
Route::page('/admin/books', 'Books/admin', ['level' => 1, 'data' => ['books' => 'list_books']]);
```

| Option | What it does |
| --- | --- |
| `data` | `name => capability`: calls each capability as the person who opens the page and passes the result to the view as `$name`. Its inputs are the query string and the route variables, the route variables winning (`/books/3?id=9` gives `get_book` the input `id = 3`). `name => ['capability', ['input' => 'value']]` gives fixed inputs instead. Only **read-only** capabilities (`readOnly: true`): opening a page never changes data, and a capability that does, or does not exist, stops the page with an error that says so |
| `signedIn` | `true`: visitors are sent to `/login` |
| `level` | people whose level (the `permissions` column) is lower are sent to `/notAuthorized` (visitors to `/login`) |
| `with` | fixed values for the view: `['with' => ['title' => 'Books']]` |
| `notFound` | a view shown, with a `404`, when a `data` capability finds nothing: `['notFound' => 'Books/missing']`. It gets the route variables, the `with` values, `$message`, `$authorized` and `$user` |

**What the view receives**, on every page: `$authorized` (true when someone is signed in), `$user` (`id`, `username`, `permissions`, or `null`), the route variables (`$id`) and the `data` and `with` values. Never the query string: a page reads it through a capability's inputs. `authorized` and `user` are reserved: a `data` or `with` entry with one of those names is an error. Capabilities return plain arrays: `$book['title']` in the view. A page on a closure route (`Route::get('/books', function () { return view('Books/index'); })`) gets none of this unless the closure passes it.

**When a capability refuses** the page's data: a visitor is sent to `/login`; a signed-in person gets `[ERROR 1006] Forbidden` (403) when the capability is not allowed to them, and `[ERROR 1005] Page Not Found` (404) otherwise (no record, or inputs the capability refuses), or the `notFound` view when the page has one.

`signedIn` and `level` need the authentication module (`Auth::authentication('User', 'users');` in `web.php`): without it, such a page fails with an error that says so.

**Pages never read models**: their data comes from capabilities, so a page, the REST API and AI agents follow the same rules. Use a [controller](#controllerroute) only when a page has logic of its own (a form post, several capabilities with conditions), and make it call capabilities too ([Calling a capability](https://github.com/caligrafy/caligrafy-quill/wiki/8.-Controllers#callcapability)).

## Controller Routing

In an MVC structure, you most likely want to route to a controller to hand off the logic to a controller class that will handle executing that logic. In order to do this, you could define a route that will call the controller:
```php
Route::get('books', 'BookController');
```
This route will redirect all calls to `http://your-app/books` to the controller `BookController`. 

You could also specify a specific method from the `BookController` to execute:
```php
Route::get('books', 'BookController@read');
```
This route would call the method `read` from `BookController`.

Controllers can be namespaced: `Route::get('orders/{id}', 'Shop\OrderController@show');` (a leading backslash is accepted). A short name that is not a declared class falls back to the example controllers of `Caligrafy\Controllers` (`'ClientController'`, `'AuthController@login'`...), and a controller or action that does not exist returns a `404`. Learn more in [Namespaced controllers](https://github.com/caligrafy/caligrafy-quill/wiki/8.-Controllers#namespacedcontrollers).

> Routes that point to a controller (`'BookController'` or `'BookController@read'`) are protected for API calls: a request that asks for JSON (`Accept: application/json`) must come from a page with the session and its CSRF token in an `X-CSRF-Token` header (`apiHeaders()`, since 2.7.0), or from a program with a personal access token as a Bearer token (`php caligrafer.php token create ...`). The legacy `API_KEY` still works where `LEGACY_API_KEY` is not `false`, for old programs only: Caligrafy Cloud turns it off. The template enables the API with `Auth::activateAPI();` at the top of `web.php`: without it, every API call to a controller answers `401`. Routes defined with a closure (`function() {...}`) are not checked. Learn more about the [REST API](https://github.com/caligrafy/caligrafy-quill/wiki/9.-Helpers-,-Forms-&-REST-API#restapi).
Later on, you will learn how to create controllers.

> #### (video) [Advanced Routing](https://www.youtube.com/watch?v=32UvcK2bSzo&list=PLsJG81fLk3_UNfTuqjCbcz1S_xbram35e)
>
> This video explains how to create more advanced routes in Caligrafy. You will learn how to create routes with parameters and how to redirect a route to a controller or to a specific action in a controller.

## Capability Routes

A capability is a feature written once and served to every experience (build once, experience everywhere). Three route methods expose capabilities instead of a controller action:
```php
Route::capability('POST', '/books', 'create_book');   // REST: one route, one capability; {id} becomes the input "id"
Route::capabilities('/capabilities');                  // GET lists, POST /capabilities/{name} calls: used by the JS client of the Vue apps
Route::mcp('/mcp');                                    // AI agents: every capability their token allows becomes an MCP tool
```
They check who calls (a personal access token or the signed-in user), validate the inputs and answer JSON with real status codes. Learn more [here](https://github.com/caligrafy/caligrafy-quill/wiki/11.-Build-Once:-Services,-Capabilities-and-MCP#rest).

## HTML Form Methods

HTML forms do not support `PUT` and `DELETE`. The framework does this for you from the moment you include a hidden `_method` field in the form with a value of `PUT` or `DELETE`. This is called **method spoofing**.

This can be achieved using regular HTML:
```html
<form action="/books" method="POST">
    <input type="hidden" name="_method" value="PUT">
</form>
```
Or, using Pug directives that will do it automatically for you:
```pug
form(action="/books", method="POST")
    =methodField('PUT')      
```
>[Next Section: Learn about Requests](https://github.com/caligrafy/caligrafy-quill/wiki/3.-Request)
````

## File: claude-plugin/skills/caligrafy/reference/03-request.md
size: 3935
sha: 4717e14f5f194faa2a6b2166ab3332a4b1c71ecc
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/03-request.md

````text
# 3. Request

> Source: Caligrafy Quill wiki - https://github.com/caligrafy/caligrafy-quill/wiki/3.-Request
> Bundled with the caligrafy-quill Claude plugin. Content preserved from the wiki; formatting cleaned.

> **Verified against framework source (see gotchas.md):**
> - `parameters` is a property AND `parameters()` a method; `fetch` is an object property (`$this->request->fetch->id`) AND a method (`fetch('id')`). See api-reference.md > Request.
> - JSON/API calls must send `Accept: application/json` (without text/html) to skip CSRF; see gotchas G1.

> **Where this fits.** Controllers and closure routes read the request directly. A [capability](https://github.com/caligrafy/caligrafy-quill/wiki/11.-Build-Once:-Services,-Capabilities-and-MCP#service) never does: it receives its inputs already checked and a `Context` that says who is calling, which is what lets the same capability answer pages, REST and AI agents.

The `Request` class provides you with the flexibility to get all the information needed about a request made to your application. The `Request` class exists in the context of a successful route. In other words, if the routing is not successful then there is no easy way to have access to the `Request` class and to leverage its powerful functionalities.
> Learn more about `Routing` [here](https://github.com/caligrafy/caligrafy-quill/wiki/2.-Routing)

## Accessing the Request
From the moment a route is successful, the `Request` class is available for use.
+ It can be accessed from the context of any `Class` created in the `/application` folder by invoking a global helper method:
```php
request();
```
+ In the context of a `Controller`, the `Request` is a property of these classes:
```php
use Caligrafy\Controller;

class BookController extends Controller {
   public function test()
   {
       dump($this->request); // will return the entire request object
   }
}
```
## Request Properties
```php
    public $method; // retrieves the request method (a POST with a _method field reports PUT or DELETE)
    public $uri; // retrieves the URI
    public $fullUri; // retrieves the the full URI including the query parameters
    public $uriComponents; // retrieves an array of the URI segments
    public $host; // retrieves the Host header
    public $authorization; // retrieves the Authorization header (Bearer personal access token of a program; pages send X-CSRF-Token with the session instead)
    public $accept; // retrieves the Accept header
    public $userAgent; // retrieves the User Agent of the request
    public $cookie; // retrieves the Cookie headers
    public $currentRoute; // retrieves the current route 
    public $currentBind; // retrieves an array of the route variables and, for GET requests, the query parameters
    public $fetch; // retrieves an object of all the parameters fetched from the request and the URI
    public $parameters; // retrieves an array of all the request parameters (the body for POST/PUT/DELETE, the query for GET)
```

## Request Methods
```php
   public function fetch($argument); //returns the value of a route variable or query parameter by name, or by position when $argument is an integer (1 for the first). Returns '' when not found
   public function files($HTMLFileName); //returns the uploaded file array ($_FILES entry) of a FORM input
   public function wantsJson(); //returns true if the request is an API call (Accept contains application/json and not text/html)
   public function parameters(); //returns an array of all the request parameters (form fields or JSON body)
```

> **Example:** for the route `Route::put('books/{id}', 'BookController@update')` and a request `PUT /books/3` with the JSON body `{"title": "Dune"}`, `$this->request->fetch('id')` returns `'3'` and `$this->request->parameters()` returns `['title' => 'Dune']`.

>[Next Section: Learn about Models](https://github.com/caligrafy/caligrafy-quill/wiki/4.-Models)
````

## File: claude-plugin/skills/caligrafy/reference/04-models.md
size: 10550
sha: 78c42a39664d4ac40b09927dbe22c7f720f6bcec
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/04-models.md

````text
# 4. Models

> Source: Caligrafy Quill wiki - https://github.com/caligrafy/caligrafy-quill/wiki/4.-Models
> Bundled with the caligrafy-quill Claude plugin. Content preserved from the wiki; formatting cleaned.

> **Verified against framework source (see gotchas.md):**
> - `create()/update()/save()` keep `0`, `''` and `false` and skip only `null`; `search()` binds the keywords; `all()` clauses are raw SQL (include the keywords) and accept `'bindings'` for `?` placeholders (gotchas G7, G2, G8). Projects on an older framework still have the old behavior.
> - FK constraint names must be unique per database and FK columns must match the parent id type (G10).

> **Where this fits.** A model is the data layer under your capabilities: one class per table. Capabilities use models in `handle()` (`(new Book('books'))->create($input)`), and every experience reaches the data through them. Create a table with its `create_db.sql` and `php caligrafer.php sql`, as in [Your first capability](https://github.com/caligrafy/caligrafy-quill/wiki/Your-First-Capability#table).

This framework is agnostic and does not engage in creating databases and database tables. The database for your application should be created using the language of your choice. This framework uses a PDO adapter to interface with your database tables. We recommend using MySQL given its tight integration with PHP but other databases can be used at your own discretion.

> #### (video) [CRUD operations on a database](https://www.youtube.com/watch?v=kNdoaSi6pcE&list=PLsJG81fLk3_UNfTuqjCbcz1S_xbram35e)
>
> In this video, we will learn how to create a database model, how to link it to Caligrafy and how to perform CRUD operations using Controllers.

## Activating Databases
In order to start using the database, you need to activate it. The `.env` file is where the database credentials need to be entered. Note that the `DB_ACTIVATE` variable in the `.env` file needs to be set to true to activate the connection.

```php

DB_ACTIVATE=true
DB_CONNECTION=mysql
DB_HOST=localhost # use `mysql` instead with Docker
DB_PORT=3306
DB_DATABASE=your-db-name
DB_USERNAME=your-db-username
DB_PASSWORD=your-db-password

```

> `DB_PORT` is added to the connection unless `DB_HOST` already contains a `;port=` setting. With Docker, use `DB_HOST=mysql` and `DB_PORT=3306` (the port inside the Docker network).

## Relational Databases
Creating databases and database tables is achieved through the database language that you normally use. This framework does not dictate how your database should be created. The examples in this documentation are all using MySQL. MySQL is the SQL language that we recommend using.

+ Your database needs to be created with all its tables before creating the `Models` in this framework:
```mysql
/* Example of a MySQL script without relationships */

CREATE TABLE IF NOT EXISTS publishers (
    id INT(25) UNSIGNED NOT NULL AUTO_INCREMENT,
    publishername varchar(255) NOT NULL,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 
    PRIMARY KEY (id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

CREATE TABLE IF NOT EXISTS books (
    id INT(25) UNSIGNED NOT NULL AUTO_INCREMENT,
    title varchar(255) NOT NULL,
    author varchar(255) NOT NULL,
    publisher_id INT(25) UNSIGNED NOT NULL, 
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 
    PRIMARY KEY (id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
```

+ The `One-to-One` and `One-to-Many` relationships need to have the appropriate foreign keys created in the database:
```mysql
/* Example of mySQL script with relationships */

CREATE TABLE IF NOT EXISTS books (
    id INT(25) UNSIGNED NOT NULL AUTO_INCREMENT,
    title varchar(255) NOT NULL,
    author varchar(255) NOT NULL,
    publisher_id INT(25) UNSIGNED NOT NULL, 
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 
    PRIMARY KEY (id), 
    CONSTRAINT fk_books_publisher FOREIGN KEY (publisher_id) REFERENCES publishers(id) ON UPDATE CASCADE ON DELETE CASCADE
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
```
+ Foreign key constraint names must be unique in the whole database (`fk_<table>_<column>` is a good habit), and a foreign key column must have exactly the same type as the `id` it references (`INT(25) UNSIGNED` here).
+ `Many-to-Many` relationships that require creating a pivot table in relational databases **DO NOT** need to be created in the database. The framework will automatically do that upon the definition of the relationship in the model.
> Learn more about creating MySQL relational databases [here](https://www.mysql.com/)

## Model Fundamentals
The `Model` is the entity that interfaces directly with the database tables.
Every table from your database that represents an object or an entity in your application that a user could act on needs to have a `Model` in the framework. For example, the table `books` needs to be represented by a `Book` Model in the framework. This framework dictates some conventions around the naming of the primary and foreign keys.
> Learn more about naming conventions [here](https://github.com/caligrafy/caligrafy-quill/wiki/1.-Getting-Started#fundamentals)
+ All the models that you create should be in the `/application/models` folder. In this folder, you can organize the models to your convenience. If you prefer to have your models in a different folder, you can always do that for as long as it is within the `/application` folder.
+ There is no formal naming convention for the `Model`. We do recommend that you remain consistent. If you choose to name the models in singular or plural then make sure to do that for all your models.
+ All the models that you create should extend the `Model` class
```php
use Caligrafy\Model;

class Book extends Model {
    // your code goes here
}
```
+ The models that you create need to have all the equivalent table attributes represented as properties
```php
use Caligrafy\Model;

class Book extends Model {
    // properties
    public $id;
    public $title;
    public $author;
    public $publisher_id;
    public $created_at;
}
```

## Interfacing with the Model
The `Model` class exists in the context of a `Controller`. In other words, you can interface with a model from within a `Controller`.

> Learn more about `Controllers` [here](https://github.com/caligrafy/caligrafy-quill/wiki/8.-Controllers)

### Instantiating a Model
In order to instantiate the model that was created in the previous section, it needs to be done from the controller by associating the controller with the model and its respective table.

```php
/* Notice that this needs to be done in the Controller */

use Caligrafy\Controller;

class BookController extends Controller {
   public function index()
   {
      $this->associate('Book', 'books'); // associate the controller with a model and a table 

      // the controller could act on the model using the model property
      dump($this->model);

   }
}
```

### Model Methods
There are several built-in methods and properties that can be used from the moment you instantiate a model

```php

/* Associations */
public $table; // retrieves the table name associated with the model
public function associate($table); // returns the Model with a database table associated to it

/* CRUD methods */
public function all($args = array()); // Returns all the records. $args can complete the query with raw SQL clauses: 'condition', 'order', 'limit' and 'bindings' (see below)
public function find($id); // returns a Model of the specific entry if available, otherwise null
public function search($scope, $keywords); // returns the records where one of the columns listed in $scope (comma separated) is LIKE $keywords. % and _ are wildcards
public function create($input); // takes an array of values or an object and adds an entry to the respective table in the database. Returns the created Model
public function update($inputs, $id); // updates an entry in the database with the new values from array or Model. Returns the updated Model
public function delete($id); // deletes a specific entry from the database
public function save(); // saves a the model into the database. If the model has an id defined then it will update otherwise it will create a new entry in the table
public function toArray(); // returns the model as an array

/* Relationships definition */

// One-to-One relationship 
public function hasOne($modelName, $joinedTableName, $foreignKey = null, $localKey = null)

// One-to-Many relationship
public function hasMany($modelName, $joinedTableName, $foreignKey = null, $localKey = null)
public function belongsTo($modelName, $joinedTableName, $foreignKey = null, $localKey = null)

// Many-to-Many relationship in both directions
public function belongsToMany($modelName, $joinedTableName, $foreignKeyLocal = null, $foreignKeyJoin = null)

```

#### Values that are saved
`create()`, `update()` and `save()` send every property of the Model (or key of the array) that is **not `null`**. `0`, `''` and `false` are saved (`false` is saved as `0`). A property left to `null` is not sent, so the column keeps its current value or its default value.

#### Queries with `all()`
The clauses given to `all()` are added to the SQL query as they are, so they must contain their SQL keywords. Never concatenate user input in them: use `?` placeholders and give the values in `bindings`.
```php
$this->all(array(
    'condition' => 'WHERE author = ? AND created_at > ?',
    'order' => 'ORDER BY title',
    'limit' => 'LIMIT 10',
    'bindings' => array($author, '2024-01-01')
));
```

#### Searching with `search()`
```php
$this->search('title,author', 'Dune%'); // title or author starting with "Dune"
$this->search('title', '%' . $keyword . '%'); // title containing $keyword
```
The keywords are sent as a bound parameter, so quotes in user input are harmless. The column names must be made of letters, digits and underscores, otherwise an exception is thrown.

#### Custom queries
For anything more specific, write a method in the Model that uses the `Database` class with bound parameters:
```php
use Caligrafy\Database;
use Caligrafy\Model;

class Book extends Model {
    // properties ...

    public function findByIsbn($isbn)
    {
        $rows = Database::toArray(Database::execute('SELECT * FROM ' . $this->table . ' WHERE isbn = ? LIMIT 1', [$isbn]));
        return !empty($rows[0])? $this->arrayToModel($rows[0]) : null;
    }
}
```

>[Next Section: Learn about Relationships](https://github.com/caligrafy/caligrafy-quill/wiki/5.-Relationships)
````

## File: claude-plugin/skills/caligrafy/reference/05-relationships.md
size: 12853
sha: 5f458de9d9cc79f25b52e08bc34ac1c6e9b9a6e9
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/05-relationships.md

````text
# 5. Relationships

> Source: Caligrafy Quill wiki - https://github.com/caligrafy/caligrafy-quill/wiki/5.-Relationships
> Bundled with the caligrafy-quill Claude plugin. Content preserved from the wiki; formatting cleaned.

> **Verified against framework source (see gotchas.md):**
> - `belongsTo()` is implemented as `hasMany()` (returns an array of rows whose FK points at this model) - for the inverse of one-to-many use `hasOne()` (gotchas G9).
> - `hasOne()` reads `<model>_id` on the CALLING model's table. `belongsToMany()` pivots copy the referenced id types and use unique constraint names (G10, fixed in the security release).

> **Where this fits.** Relationships live in the models, so a capability can return a book with its author (`$book->author()`) and every experience gets the same data.

In relational databases, tables are related to one another. There are several types of relationships that can be defined. This framework makes it easy to define and use relationships without having to write extensive code.
There are 3 types of relationships defined in this framework: `One-to-One`, `One-to-Many` and `Many-to-Many`.

> #### (video) [Relationships](https://www.youtube.com/watch?v=o6knV88iCkw&list=PLsJG81fLk3_UNfTuqjCbcz1S_xbram35e)
>
> In this video, we will learn how to define one-to-one, one-to-many and many-to-many relationships among models in Caligrafy.

## One-To-One Relationship

A `One-to-One` relationship is the simplest relationship between two entities. For example, we can have an authorization module where every `User` has one `Account` and reciprocally every `Account` has one `User`. This is a typical `One-to-One` relationship.

### Declaration
In order to create such a relationship, you first need to make sure that the database tables that you create for the two entities have the foreign keys that link them. `hasOne()` reads the foreign key **on the table of the model that calls it**: `$user->account()` uses `users.account_id` and `$account->user()` uses `accounts.user_id`. If you only need one direction, you only need that foreign key.

> Learn more about `Relational Databases` [here](https://github.com/caligrafy/caligrafy-quill/wiki/4.-Models)

Once your database tables are created, one of the models `User`, for example, will add a new method that invokes the `hasOne()` method to define that relationship. 
```php
use Caligrafy\Model;

class User extends Model {

    // properties go here
    //...

    //new method that defines a One-to-One relationship
    public function account()
    {
       return $this->hasOne('Account','accounts');
       // the first argument is the model name to link to
       // the second argument is the database table name of to link to

    }
}
```
### Inverse Relationship
In the `Account` model, you will need to do the same using the `hasOne` method.
```php
use Caligrafy\Model;

class Account extends Model {

    // properties go here
    //...

    //new method that defines the inverse of a One-to-One relationship
    public function user()
    {
       return $this->hasOne('User','users');
       // the first argument is the model name to link to
       // the second argument is the database table name of to link to

    }
}
```
> **Notice:** In both the examples, there is no restriction as to the name of the method used to define the relationships. We recommend using a name that is representative enough of the type of relationship.

### Usage
After the relationship has been defined in the respective models, making use of that relationship takes place in the context of the `Controller` or in the 'Model' itself in the constructor or the invoke functions. For example, in order to get the `Account` associated with a `User`, you would do the following in the `UserController`:
```php
use Caligrafy\Controller;

class UserController extends Controller {

    public function getAccount() 
    {
        $this->associate('User', 'users'); // create the association
        $account = $this->find(1)->account(); // returns account for user with id 1
    }
}
```
## One-To-Many Relationship
A `One-to-Many` relationship is a common type of relationship between two entities. Taking the same example as before, you could allow one `User` to have multiple `Account` but an `Account` to belong to one user. For that you would want to use a `One-to-Many` relationship.

### Declaration
In order to create such a relationship, you first need to make sure that the entity that could exist many times has a reference in the table of the other entity. In our example, that means that there should be a `foreign_key` in the `accounts` table that references the `users` table.

> Learn more about `Relational Databases` [here](https://github.com/caligrafy/caligrafy-quill/wiki/4.-Models)

Once your database tables are created, the `User` model needs a new method that invokes the `hasMany()` method to define that relationship. 
```php
use Caligrafy\Model;

class User extends Model {

    // properties go here
    //...

    //new method that defines a One-to-Many relationship
    public function accounts()
    {
       return $this->hasMany('Account','accounts');
       // the first argument is the model name to link to
       // the second argument is the database table name of to link to

    }
}
```
### Inverse Relationship
In the `Account` model, the account belongs to one `User`. Use the `hasOne` relationship: it reads `accounts.user_id` and returns the `User`.

> **Notice:** `belongsTo()` is not the inverse of `hasMany()` in Caligrafy: it behaves exactly like `hasMany()` (it looks for `<thismodel>_id` in the joined table and returns an array). Use `hasOne()` for the inverse of a One-to-Many relationship.
```php
use Caligrafy\Model;

class Account extends Model {

    // properties go here
    //...

    //new method that defines the One to relationship
    public function user()
    {
       return $this->hasOne('User','users');
       // the first argument is the model name to link to
       // the second argument is the database table name of to link to

    }
}
```
> **Notice:** In both the examples, there is no restriction as to the name of the method used to define the relationships. We recommend using a name that is representative enough of the type of relationship.

### Usage
After the relationship has been defined in the respective models, making use of that relationship takes place in the context of the `Controller` or in the 'Model' itself in the constructor or the invoke functions. For example, in order to get all the accounts associated with a `User`, you would do the following in the `UserController`:
```php
use Caligrafy\Controller;

class UserController extends Controller {

    public function getAccounts() 
    {
        $this->associate('User', 'users'); // create the association
        $accounts = $this->find(1)->accounts(); // returns accounts for user with id 1
    }
}
```
## Many-to-Many Relationship
A `Many-to-Many` relationship ensures that two entities could have multiple instances of each other. Taking the same example as before, you could allow one `User` to have multiple `Account` and one `Account` to have multiple `User`. For that you would want to use a `Many-to-Many` relationship.

### Declaration
In order to create such a relationship, you do not need to do anything in the database other than making sure to follow the naming conventions.

> Learn more about Naming Conventions [here](https://github.com/caligrafy/caligrafy-quill/wiki/1.-Getting-Started)

The `User` model needs a new method that invokes the `belongsToMany()` method to define that relationship. 
```php
use Caligrafy\Model;

class User extends Model {

    // properties go here
    //...

    //new method that defines a Many-to-Many relationship
    public function accounts()
    {
       return $this->belongsToMany('Account','accounts');
       // the first argument is the model name to link to
       // the second argument is the database table name of to link to

    }
}
```
### Inverse Relationship
Similarly, the `Account` model needs to define a `belongsToMany` relationship.
```php
use Caligrafy\Model;

class Account extends Model {

    // properties go here
    //...

    //new method that defines a Many-to-Many relationship
    public function users()
    {
       return $this->belongsToMany('User','users');
       // the first argument is the model name to link to
       // the second argument is the database table name of to link to

    }
}
```
> **Notice:** In both the examples, there is no restriction as to the name of the method used to define the relationships. We recommend using a name that is representative enough of the type of relationship.

### Usage
After the relationship has been defined in the respective models, making use of that relationship takes place in the context of the `Controller` or in the 'Model' itself in the constructor or the invoke functions. For example, in order to get all the accounts associated with a `User`, you would do the following in the `UserController`:
```php
use Caligrafy\Controller;

class UserController extends Controller {

    public function getAccounts() 
    {
        $this->associate('User', 'users'); // create the association
        $user = $this->find(1);
        $accounts = $user->accounts(); // returns all accounts for user with id 1

        // Alternatively, you can use the pivot property to access all the pivots associated with a user
        $accounts = $user->pivot->accounts->all; // returns all accounts for user with id 1
    }
}
```

### The pivot table
The pivot table is created the first time the relationship method is called:
+ it is named `<table1>_<table2>` (`users_accounts` when called from `User`), or `<table2>_<table1>` if that table already exists
+ it has the columns `id`, `user_id`, `account_id`, `created_at` and `modified_at`. The foreign key columns copy the type of the `id` columns they reference, so `INT` and `INT UNSIGNED` ids both work
+ its foreign key constraints are named `fk_<pivot>_<column>`, which keeps them unique in the database

If you prefer to create the pivot table yourself (to add columns for example), create it with the same name and columns before calling the relationship: the framework will leave it untouched.

### Attaching and detaching
`attach()` and `detach()` add and remove links in the pivot table. Call the relationship method first so that the model knows its pivot, then give the **joined table name** and the id to link. Both return the refreshed list of related models.
```php
$user = $this->find(1);
$user->accounts();                       // loads the relationship and its pivot
$accounts = $user->attach('accounts', 5); // links account 5 to user 1
$accounts = $user->detach('accounts', 5); // removes the link
```

## Overriding Naming Convention
Relationships work under the premise that all tables have and 'id' primary key. This convention cannot be overridden. Also, the relationship methods assume that all foreign keys have the format `modelname_id` with the model name being in small caps. Taking the `User` and `Account` example again, the foreign keys are by default assumed to be `user_id` (in the `accounts` table) and `account_id` (in the `users` table).

This framework provides the flexibility to override the foreign keys naming.
```php
// One-to-One relationship 
public function hasOne($modelName, $joinedTableName, $foreignKey = null, $localKey = null)

// One-to-Many relationship
public function hasMany($modelName, $joinedTableName, $foreignKey = null, $localKey = null)
public function belongsTo($modelName, $joinedTableName, $foreignKey = null, $localKey = null)

// Many-to-Many relationship in both directions
public function belongsToMany($modelName, $joinedTableName, $foreignKeyLocal = null, $foreignKeyJoin = null)

```
## Model Methods
The core `Model` class comes prepackaged with a set of properties and methods that help establish the relationships and interfacing with joined tables.
```php
// One-to-One relationship 
public function hasOne($modelName, $joinedTableName, $foreignKey = null, $localKey = null)

// One-to-Many relationship
public function hasMany($modelName, $joinedTableName, $foreignKey = null, $localKey = null)
public function belongsTo($modelName, $joinedTableName, $foreignKey = null, $localKey = null)

// Many-to-Many relationship in both directions
public function belongsToMany($modelName, $joinedTableName, $foreignKeyLocal = null, $foreignKeyJoin = null)
public function attach($joinedTableName, $id) // attaches an ID to a joined many-to-many table
public function detach($joinedTableName, $id) // detaches and ID from a joined many-to-many table 

```

>[Next Section: Learn about Validation](https://github.com/caligrafy/caligrafy-quill/wiki/6.-Validation)
````

## File: claude-plugin/skills/caligrafy/reference/06-validation.md
size: 12562
sha: 6fbb7a7fdd38c68d1d0b345d5f1c1418ed72188e
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/06-validation.md

````text
# 6. Validation

> Source: Caligrafy Quill wiki - https://github.com/caligrafy/caligrafy-quill/wiki/6.-Validation
> Bundled with the caligrafy-quill Claude plugin. Content preserved from the wiki; formatting cleaned.

> **Verified against framework source (see gotchas.md):**
> - `check()` returns an ARRAY both on success with filters (the filtered data) and on failure (errors). Prefer `filter()` then `validate()` (gotchas G6).
> - Rules other than `required` are skipped for empty values (verified by the QA suite).

> **Where this fits.** These GUMP rules and filters are what you write in the `$rules` and `$filters` of a [capability](https://github.com/caligrafy/caligrafy-quill/wiki/11.-Build-Once:-Services,-Capabilities-and-MCP#service): the framework applies them on every call, and pages, REST and AI agents all get the same message for each field. The rules also become the input schema that agents and the JS client see. Controllers can still use them directly, as below.

Data validation is key to the success of any application. This framework provides a tight integration with GUMP - a third party library that makes data validation easy and painless.

> Learn more about GUMP [here](https://github.com/Wixel/GUMP)

GUMP provides two powerful features that are the cornerstones for keeping the data clean and secure: validation and filtering. Both mechanisms use built-in rules that are easy to use and apply based on the type of data.

## Data Validation
`Validation` is about checking that the input follows the rules that the application is expecting. It is intended to avoid undesired data that could ultimately cause errors or introduce security vulnerabilities.

### Validator Methods

There are 3 controller methods that could help validate inputs that we suggest using.

```php

   public function validate($inputArray, $ruleArray); 
   // This methods validates the input array based on a set of rules and returns true or an array of error messages keyed by field

   GUMP::is_valid($inputArray, $ruleArray, $customMessageArray); // also callable as $this->validator->is_valid(...)
   // This method validates the input array and uses custom error messages. It returns true or an array of readable (HTML) error messages

   public function check(Array $inputArray, Array validation_rules, Array filtering_rules); 
   // This method combines both validation and filtering (more on this later)

```

### Validators
GUMP relies on a set of rules - the `Validators` - to do this validation.
Below is a curated list of the available `Validators`.

* required `Ensures the specified key value exists and is not empty`
* required_file `Ensures that a file is specified`
* valid_email `Checks for a valid email address`
* max_len,n `Checks key value length, makes sure it's not longer than the specified length. n = length parameter.`
* min_len,n `Checks key value length, makes sure it's not shorter than the specified length. n = length parameter.`
* exact_len,n `Ensures that the key value length precisely matches the specified length. n = length parameter.`
* alpha `Ensure only alpha characters are present in the key value (a-z, A-Z)`
* alpha_numeric `Ensure only alpha-numeric characters are present in the key value (a-z, A-Z, 0-9)`
* alpha_dash `Ensure only alpha-numeric characters + dashes and underscores are present in the key value (a-z, A-Z, 0-9, _-)`
* alpha_space `Ensure only alpha-numeric characters + spaces are present in the key value (a-z, A-Z, 0-9, \s)`
* numeric `Ensure only numeric key values`
* integer `Ensure only integer key values`
* boolean `Checks for PHP accepted boolean values, returns TRUE for "1", "true", "on" and "yes"`
* float `Checks for float values`
* valid_url `Check for valid URL or subdomain`
* url_exists `Check to see if the url exists and is accessible`
* valid_cc `Check for a valid credit card number (Uses the MOD10 Checksum Algorithm)`
* valid_name `Check for a valid format human name`
* contains,n `Verify that a value is contained within the pre-defined value set`
* min_numeric `Determine if the provided numeric value is higher or equal to a specific value`
* max_numeric `Determine if the provided numeric value is lower or equal to a specific value`
* date `Determine if the provided input is a valid date (ISO 8601)`
* starts `Ensures the value starts with a certain character / set of character`
* phone_number `Validate phone numbers that match the following examples: 555-555-5555 , 5555425555, 555 555 5555, 1(519) 555-4444, 1 (519) 555-4422, 1-555-555-5555`
* regex `You can pass a custom regex using the following format: 'regex,/your-regex/'`
* valid_json_string `validate string to check if it's a valid json format`

> See the full list of GUMP Validators [here](https://github.com/Wixel/GUMP/blob/master/README.md)

### Usage 
`Validation` can be used within the context of a `Controller` or any class in the `/application` folder.
This framework integrated the main methods of GUMP into one common method. 

Let's take the example of a `User` model that has a username and a password. Both the username and the password are required. The username must be alphanumeric and the password should be between 6 and 100 characters.
```php
use Caligrafy\Controller;

class UserController extends Controller {
    public function read()
    {

       /* Validate the inputs using the check the inputs according to the described rules 
        * @param array inputArray is the array of inputs
        * @param array field => rule
        * @return true if validation is successful. Otherwise an array of error messages is returned for each erroneous field
        */
       $result = $this->check($inputArray, array(
                    'username' => 'required|alpha_numeric',
	            'password' => 'required|max_len,100|min_len,6')
       );

       // Alternatively, you can use the validate method
      $result = $this->validate($inputArray, array(
                    'username' => 'required|alpha_numeric',
	            'password' => 'required|max_len,100|min_len,6')
       );

       // With Custom Error messages
      $result = $this->validator->is_valid($inputArray, 
                array(
                    'username' => 'required|alpha_numeric',
	            'password' => 'required|max_len,100|min_len,6'
                ),
                array(
                    'username' => ['required' => 'Username is required']
                )
              );

       // Checking if valid
       if ($result !== true) {
           return $result;
       } else {//...}
    }
}

```

## Data Filtering
Filtering is used to make sure that the data is clean, sanitized and that it does not cause any undesired errors or security vulnerabilities. Unlike validation that is meant to either accept or reject the data, filtering is meant to modify the data and clean it from undesirable formats.

### Filters
GUMP relies on a set of filtering rules - the `Filters`. `Filters` can be any PHP functions that return a string. According to the GUMP documentation, you don't need to create your own if a native PHP function exists that does what you want the filter to do.
Below is a curated list of the available `Filters`.

* sanitize_string `Remove script tags and encode HTML entities, similar to GUMP::xss_clean();`
* urlencode `Encode url entities`
* htmlencode `Encode HTML entities`
* sanitize_email `Remove illegal characters from email addresses`
* sanitize_numbers `Remove any non-numeric characters`
* sanitize_floats `Remove any non-float characters`
* trim `Remove spaces from the beginning and end of strings`
* base64_encode `Base64 encode the input`
* base64_decode `Base64 decode the input`
* sha1 `Encrypt the input with the secure sha1 algorithm`
* md5 `MD5 encode the input`
* noise_words `Remove noise words from string`
* json_encode `Create a json representation of the input`
* json_decode `Decode a json string`
* rmpunctuation `Remove all known punctuation characters from a string`
* basic_tags `Remove all layout orientated HTML tags from text. Leaving only basic tags`
* whole_number `Ensure that the provided numeric value is represented as a whole number`
* ms_word_characters `Converts MS Word special characters [“”‘’–…] to web safe characters`
* lower_case `Converts to lowercase`
* upper_case `Converts to uppercase`
* slug `Creates web safe url slug`

### Usage
`Filtering` can be used within the context of a `Controller` or any class in the `/application` folder.
This framework integrated the main methods of GUMP into one common method. 

Let's take the example of a `User` model that has a username and a password. Both the username and password need to be trimmed (remove extra spaces) and sanitized in order to make sure that no scripts are included in them. This is a great way to use filters.
```php
use Caligrafy\Controller;

class UserController extends Controller {
    public function read()
    {

       /* Validate the inputs using the check the inputs according to the described rules 
        * @param array inputArray is the array of inputs
        * @param array validation is array() by default if no validation is needed
        * @param array field => filter_rules
        * @return array filtered results
        */
       $result = $this->check($inputArray, array(), array(
                    'username' => 'trim|sanitize_string',
	            'password' => 'trim|sanitize_string')
       );

       // Alternatively, you can use the filter method
       $result = $this->filter($inputArray, array(
                    'username' => 'trim|sanitize_string',
	            'password' => 'trim|sanitize_string')
       );       
    }
}
```
> Notice that the 2nd argument of the `check` method is an empty array. This is because it is a combined routine that could execute both validation and/or filtering. 

## Validation & Filtering

The `check` method is a framework method that makes the usage of the GUMP validator more straightforward.

`public function check(Array $inputArray, Array validation_rules, Array filtering_rules);`

The check method accepts 3 arrays: 
1) the input array to be checked 
2) validation array
3) filtering array.

+ When both arrays are provided, the `check` function filters and validates at once. If the validation succeeds, it returns an array of the filtered fields. If the validation fails, it returns an array of the erroneous fields with their respective error messages. **Both outcomes are arrays**, so `$result !== true` cannot tell them apart in this mode. When you need both, filter first and then validate:
```php
$data = $this->filter($inputArray, array('username' => 'trim|sanitize_string'));
$result = $this->validate($data, array('username' => 'required|alpha_numeric'));
if ($result !== true) {
    return api(array('errors' => $result)); // $result is ['username' => 'error message']
}
// $data holds the clean values
```
+ When the validation array is given but not the filtering array, the method performs a validation as described earlier.
+ When the filtering array is given but not the validation array, the method performs a filter as described earlier.

+ Rules other than `required` are skipped when a field is empty: `'passcode' => 'min_len,8'` accepts an empty passcode. Add `required` when the field is mandatory.

> GUMP provides more elaborate features to create custom validations and filters. 
> For more details, consult the GUMP library documentation [here](https://github.com/Wixel/GUMP/blob/master/README.md)

## File Validation
If your application requires uploading files, you might want to validate the files before accepting that. The `uploadFile()` helper also refuses any extension that is not in its allow-list (images, documents, audio, video, `zip` and `json` by default, never scripts). Learn more about [helpers](https://github.com/caligrafy/caligrafy-quill/wiki/9.-Helpers-,-Forms-&-REST-API#helpers).
Using the same `check` method, you can use the built-in rules to perform this validation.
```php
use Caligrafy\Controller;

class UserController extends Controller {
    public function read()
    {
       // Validate the $_FILES inputs
       $result = $this->check($_FILES, array(
	'image' => 'required_file|extension,png;jpg')
       );

       // Then move the file to public/uploads
       if ($result === true) {
           $url = uploadFile($this->request->files('image'), array('png', 'jpg'));
       }
    }
}
```

>[Next Section: Learn about Views](https://github.com/caligrafy/caligrafy-quill/wiki/7.-Views)
````

## File: claude-plugin/skills/caligrafy/reference/07-views.md
size: 25128
sha: 0731d578e32c34dd298f4bcfbf824c2f631ae6cd
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/07-views.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/07-views.md

## File: claude-plugin/skills/caligrafy/reference/08-controllers.md
size: 14143
sha: 3f1552273ba2ba60f1558d4dbe02fcf528b77eaf
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/08-controllers.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/08-controllers.md

## File: claude-plugin/skills/caligrafy/reference/09-helpers-forms-rest-api.md
size: 23723
sha: 26ff6487657c64ed56f695198b3cf3f7167ea516
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09-helpers-forms-rest-api.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09-helpers-forms-rest-api.md

## File: claude-plugin/skills/caligrafy/reference/09.1-authentication.md
size: 15087
sha: 5163b8462b694166382f9cef64cdd457ad9cc905
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.1-authentication.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.1-authentication.md

## File: claude-plugin/skills/caligrafy/reference/09.2-stripe-crypto-payment.md
size: 20953
sha: 4a29d3f5831cbb55f44541deaef282be360ccc2a
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.2-stripe-crypto-payment.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.2-stripe-crypto-payment.md

## File: claude-plugin/skills/caligrafy/reference/09.3-metadata-rich-cards.md
size: 5311
sha: 49f526c730ef70bef2b36cbfd2d3777338c91aa0
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.3-metadata-rich-cards.md

````text
# 9.3 Metadata & Social Rich Cards

> Source: Caligrafy Quill wiki - https://github.com/caligrafy/caligrafy-quill/wiki/9.3-Metadata-&-Social-Rich-Cards
> Bundled with the caligrafy-quill Claude plugin. Content preserved from the wiki; formatting cleaned.

Every application deserves to be promoted the proper way. This framework provides you with out-of-the-box features that can help you define proper Metadata for your application that can be automatically turned into **rich cards** when shared on social platforms such as Facebook, LinkedIn, Pinterest or Twitter.

> #### (video) [Creating Rich Cards with Caligrafy](https://youtu.be/qgzufCEcg_I)
>
> In this video, we show how we can create Rich Cards that give search engines, social media networks and collaboration tools the ability to have more information about your site, application or product from the URL alone.

## The social card of every page
Since 2.16.2 every page built on `default/master/site` prints the app's card in its `head`: what Facebook, LinkedIn,
X, Slack or iMessage show when one of its addresses is shared. Set it once, in `application/config/config.php`, with the
core helper `metadata()` (new projects have the example there, commented):

```php
metadata(array(
    'site_name' => 'Bookshelf',
    'title' => 'Bookshelf: your books, lent and returned',
    'description' => 'Know who has your books and when they come back.',   // under 125 characters
    'image' => 'https://bookshelf.caligrafy.com/public/images/resources/social-card.png',   // 1200x630
    'image_alt' => 'Bookshelf',
    // 'site_url' => 'https://bookshelf.caligrafy.com',   // APP_URL by default
));
```

| Key | Becomes | |
|---|---|---|
| `title` | `og:title`, `twitter:title` | Required: nothing is printed without it |
| `description` | `description`, `og:description`, `twitter:description` | Under 125 characters; X and LinkedIn do not show it |
| `image` | `og:image`, `twitter:image` (a large card) | 1200x630 (`image_width`, `image_height` for another size) |
| `image_alt` | `og:image:alt`, `twitter:image:alt` | What the image shows |
| `site_name` | `og:site_name` | |
| `site_url` | `og:url`: this address plus the page's path | `APP_URL` by default (Caligrafy Cloud sets it) |

X and LinkedIn show only the image, the title and the domain: say what the app does in the title. A page can have its
own card: its controller passes the keys to its view as `'metadata'`
(`view('Books/show', array('metadata' => array('title' => $book->title) + metadata()))`).

Before 2.16.2 the card had a type Facebook refuses (`og:product`), a 600x317 image and placeholder values, and only the
pages that passed `'metadata' => metadata()` to their view showed it. The older layout `layoutblocks.pug` still prints
the card only when its view gets `'metadata'`.

## Activating Social Sharing
Most applications today give the ability for users to share the application to social networks directly from the application. This is a neat way for promoting the application to your friends and fans on social media. 
This framework comes prepackage with a view module that gets injected into the provided default template. This module can be found in `/application/views/default/modules/social.pug`. 

In order to activate the Social Share buttons in the default template, all you need to do is pass the boolean variable `$social` to the `view`. The previous `return` statement will now look like this:
```php

return view('/default/index', array('metadata' => metadata(), 'social' => true)); 

```
Provided you created the appropriate `route` to point to this `controller` that we created, you will be able to test out the page in your browser and you will see the social sharing buttons at the bottom center of the page.

## Testing Sharing and Rich Cards

Social Sharing can be tested directly by clicking on any of the social buttons that got added in the previous steps. Upon sharing, if the metadata that was defined is accurate, you would be able to see a rich card with all the descriptive information that you provided. In order for this to work, your machine needs to be publicly accessible which means that it cannot be tested with the `localhost`. It needs to be on either a production server with a registered host name or the machine IP needs to be public. 

> If you would like to make your localhost publicly accessible, we recommend using [ngrok.com](https://ngrok.com/).

You could also test out the rich cards on each social network using their debuggers, or [opengraph.xyz](https://www.opengraph.xyz/) for all of them. For that, your machine also needs to be publicly accessible. Platforms keep a card for days: after a change, press **Scrape again** in Facebook's debugger.

+ **facebook**: [facebook debugger](https://developers.facebook.com/tools/debug/sharing/)
+ **google**: [google rich results test](https://search.google.com/test/rich-results)
+ **X**: no validator any more: preview a post with the address
+ **linkedin**: [linkedin post inspector](https://www.linkedin.com/post-inspector/)
+ **pinterest**: [pinterest url debugger](https://developers.pinterest.com/tools/url-debugger/)

>[Next Section: Learn about Search Referencing and Analytics](https://github.com/caligrafy/caligrafy-quill/wiki/9.4-Search-Referencing-and-Analytics)
````

## File: claude-plugin/skills/caligrafy/reference/09.4-search-referencing-analytics.md
size: 3878
sha: 815ca510c7e8d6e439ddbccc8a702a9dd96e1504
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.4-search-referencing-analytics.md

````text
# 9.4 Search Referencing and Analytics

> Source: Caligrafy Quill wiki - https://github.com/caligrafy/caligrafy-quill/wiki/9.4-Search-Referencing-and-Analytics
> Bundled with the caligrafy-quill Claude plugin. Content preserved from the wiki; formatting cleaned.

For any application today to be searchable through search engines like Google for example, it needs to be tagged properly. Caligrafy provides you with some helper methods and out-of-the-box views that help you reference your application pages properly.

## Metadata
Having adequate metadata and structured data is key for search engines to know more about the content of your pages. Caligrafy comes prepackaged with methods and view modules that could help you achieve that easily.

> Learn more about `Metadata Definition` [here](https://github.com/caligrafy/caligrafy-quill/wiki/9.3-Metadata-&-Social-Rich-Cards)

## Keywords
The most basic metadata that your pages need to have are `keywords`. Search engine algorithms rely on the `keywords` metadata to ultimately display your pages in the appropriate context.
In order to set the keywords for your application, Caligrafy provides a core helper method `keywords(String keywords)` that takes string of keywords separated by comas.

In order to set the keywords, you could either call the `keywords()` methods from the controller handling the page or if you want to set the same keywords for all your pages, you can call the method once from `/application/config/config.php`.

```php
keywords('keyword1, keyword2, keyword3');

``` 
if you are using the `master` views or the view modules provided by Caligrafy, then all you need to do is activate the metadata by sending it to the view and the defined keywords will be sent along with the other metadata.

> Learn more about `Metadata Definition` [here](https://github.com/caligrafy/caligrafy-quill/wiki/9.3-Metadata-&-Social-Rich-Cards)

## Linking your application to Google
Caligrafy uses Google as the primary engine for search referencing out-of-the-box. When you create a `Site property` in the Google Admin Console, you first need to verify that the site belongs to you. For that, Google usually asks you to include a piece of code in your pages. With Caligrafy, you don't have to worry about that. If you are using the `master` views or the view modules provided by Caligrafy, you need to do 2 things:

1- Add the `google-site-verification` code to your metadata
> Learn more about `Metadata Definition` [here](https://github.com/caligrafy/caligrafy-quill/wiki/9.3-Metadata-&-Social-Rich-Cards)

2- Send the metadata to the view

```php
use Caligrafy\Controller;

class MyController extends Controller {
    public function index()
    {
       return view('default/index', array('metadata' => metadata());
    }
}

```

## Linking your application with Google Analytics
If you want to connect your application with Google Analytics to get more insights on your application usage, you can do this very easily with Caligrafy from the moment you create a Google Analytics account and that you get a `Google Analytics ID`. You will be able to activate analytics in 2 steps:

1- Add the Google Analytics ID in the `.env` file: `GOOGLE_ANALYTICS_ID=<your id>`

> For Google Analytics to work, your application needs to be on a production server. In the `.env`, the APP_ENV must be set to `production`, otherwise the analytics snippet code will not be included. 

2- Activate analytics for the pages that you want to track by sending the activation to the view
```php
use Caligrafy\Controller;

class MyController extends Controller {
    public function index()
    {
       return view('default/index', array('metadata' => metadata(),'google_analytics' => true));
    }
}

```

>[Next Section: Learn about Security and Upgrading](https://github.com/caligrafy/caligrafy-quill/wiki/9.5-Security-and-Upgrading)
````

## File: claude-plugin/skills/caligrafy/reference/09.5-security-and-upgrading.md
size: 29818
sha: 23d2ee99edd47a688f18e93dd8717fa083632708
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.5-security-and-upgrading.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.5-security-and-upgrading.md

## File: claude-plugin/skills/caligrafy/reference/09.7-vue-flow-of-information.md
size: 3415
sha: e5bd9fc14cbf2adf6242fd2e525c2d88ca718f91
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.7-vue-flow-of-information.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.7-vue-flow-of-information.md

## File: claude-plugin/skills/caligrafy/reference/09.7.0-vue-as-library-setup.md
size: 2613
sha: 6608dd63ba00ee8d60fae59673593b9a8751f5bb
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.7.0-vue-as-library-setup.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.7.0-vue-as-library-setup.md

## File: claude-plugin/skills/caligrafy/reference/09.7.1-vue-structure.md
size: 6240
sha: a2ce8286bfba227914e414686bd58b61de517bad
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.7.1-vue-structure.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.7.1-vue-structure.md

## File: claude-plugin/skills/caligrafy/reference/09.7.2-vue-routes.md
size: 4528
sha: 173518d7af4c797c147b67abf83129842dce1fe8
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.7.2-vue-routes.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.7.2-vue-routes.md

## File: claude-plugin/skills/caligrafy/reference/09.7.3-vue-requests.md
size: 7631
sha: e4fc87443a6bee2969d91681ab1a0a92f6396b06
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.7.3-vue-requests.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.7.3-vue-requests.md

## File: claude-plugin/skills/caligrafy/reference/09.7.4-vue-forms.md
size: 4833
sha: 9be9c726ca2f0d10443b07ef1370a549217c5330
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.7.4-vue-forms.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.7.4-vue-forms.md

## File: claude-plugin/skills/caligrafy/reference/09.7.5-vue-validations.md
size: 2992
sha: 27c83f9af8148a01b4579e0c6ba226a273cf1414
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.7.5-vue-validations.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.7.5-vue-validations.md

## File: claude-plugin/skills/caligrafy/reference/09.7.6-vue-components.md
size: 8047
sha: 5bc6a86bb35cae3a6296fcecce09d9541e5fecb9
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.7.6-vue-components.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.7.6-vue-components.md

## File: claude-plugin/skills/caligrafy/reference/09.8.1-vue-spa-setup.md
size: 8973
sha: 6a798cb3a100a79c4f1ed3c67b1ad49b1db9c68e
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.8.1-vue-spa-setup.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.8.1-vue-spa-setup.md

## File: claude-plugin/skills/caligrafy/reference/09.8.2-vue-spa-routing.md
size: 5677
sha: 957176369a038428f6187c3bc215e14fc8c42397
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.8.2-vue-spa-routing.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.8.2-vue-spa-routing.md

## File: claude-plugin/skills/caligrafy/reference/09.8.3-vue-single-file-components.md
size: 6966
sha: 8db4996804dd25194ade5c32a7ff1f0b1dca03ec
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.8.3-vue-single-file-components.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.8.3-vue-single-file-components.md

## File: claude-plugin/skills/caligrafy/reference/09.8.4-vue-state-management.md
size: 5703
sha: 43fd91dd79465b4108237cfd02ea95b185d6d4d3
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.8.4-vue-state-management.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.8.4-vue-state-management.md

## File: claude-plugin/skills/caligrafy/reference/09.9.1-ai-watson-bots.md
size: 16251
sha: a52dff5c800c1fcfc28c7225a591137cc790c93c
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.9.1-ai-watson-bots.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.9.1-ai-watson-bots.md

## File: claude-plugin/skills/caligrafy/reference/09.9.2-ai-face-detection.md
size: 38971
sha: 50c160c57a05216ad4254cd5b98af54fffa7df1f
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.9.2-ai-face-detection.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.9.2-ai-face-detection.md

## File: claude-plugin/skills/caligrafy/reference/09.9.3-ai-machine-learning.md
size: 17266
sha: 5778b47ba1f07a0576d599d45d2e8815d3e28140
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.9.3-ai-machine-learning.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.9.3-ai-machine-learning.md

## File: claude-plugin/skills/caligrafy/reference/09.9.4-ai-openai-chatgpt-dalle.md
size: 12418
sha: 792d450400dba375190c667c2aa02e14e4fd2e17
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.9.4-ai-openai-chatgpt-dalle.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.9.4-ai-openai-chatgpt-dalle.md

## File: claude-plugin/skills/caligrafy/reference/09.9.5-ai-openai-assistants.md
size: 13016
sha: f73aed4de53b6ff0804cf9870f7bd4e6d5e4861c
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.9.5-ai-openai-assistants.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.9.5-ai-openai-assistants.md

## File: claude-plugin/skills/caligrafy/reference/09.9.6-ai-open-source-agents-groq.md
size: 16713
sha: 0da68f0e656159ae9ba58d6a9561b7e9d6e5b13a
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.9.6-ai-open-source-agents-groq.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/09.9.6-ai-open-source-agents-groq.md

## File: claude-plugin/skills/caligrafy/reference/10-claude-code-plugin.md
size: 23078
sha: 1fbb1234a2c7e43830bd0d4b981d3f8eb1076f4b
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/10-claude-code-plugin.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/10-claude-code-plugin.md

## File: claude-plugin/skills/caligrafy/reference/11-release-notes-october-2026.md
size: 37638
sha: d25e4962f75149d2c153b2f7fa41523633ea362a
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/11-release-notes-october-2026.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/11-release-notes-october-2026.md

## File: claude-plugin/skills/caligrafy/reference/12-testing-caligrafy.md
size: 20033
sha: 3f2e999fca1062fa06dcbc7946823ebc4063d380
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/12-testing-caligrafy.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/12-testing-caligrafy.md

## File: claude-plugin/skills/caligrafy/reference/13-build-once-services-capabilities-mcp.md
size: 17204
sha: 4887ee7aae91fbf7931414ef865116ae457c3df9
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/13-build-once-services-capabilities-mcp.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/13-build-once-services-capabilities-mcp.md

## File: claude-plugin/skills/caligrafy/reference/14-release-notes-2.1.0.md
size: 5642
sha: 726244bf30970e3d5498f8da9016167128317112
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/14-release-notes-2.1.0.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/14-release-notes-2.1.0.md

## File: claude-plugin/skills/caligrafy/reference/15-quickstart.md
size: 9546
sha: d216f8e2a44711a26a6cc99f7667884f39d60844
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/15-quickstart.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/15-quickstart.md

## File: claude-plugin/skills/caligrafy/reference/16-how-caligrafy-works.md
size: 7716
sha: 7d483d77bf0e413b0cdefddd040c5febc1ba8203
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/16-how-caligrafy-works.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/16-how-caligrafy-works.md

## File: claude-plugin/skills/caligrafy/reference/17-your-first-capability.md
size: 9456
sha: 6183fdbeaf8e73bb98d33c1f219ab15b2ed869d1
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/17-your-first-capability.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/17-your-first-capability.md

## File: claude-plugin/skills/caligrafy/reference/18-caligrafer.md
size: 29735
sha: 176736b1e33aa3bdccb7b8aae16897e164ca493c
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/18-caligrafer.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/18-caligrafer.md

## File: claude-plugin/skills/caligrafy/reference/19-release-notes-2.3.0.md
size: 3544
sha: f86a63d70b03a1fe474a8e89c77b8a97d1b60091
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/19-release-notes-2.3.0.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/19-release-notes-2.3.0.md

## File: claude-plugin/skills/caligrafy/reference/20-putting-it-online.md
size: 14977
sha: 2c4c391cb5ef75a06a9652b28379ef87a99b04f4
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/20-putting-it-online.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/20-putting-it-online.md

## File: claude-plugin/skills/caligrafy/reference/21-release-notes-2.4.0.md
size: 4043
sha: c3b38360e9dc1db31f7075ccc2325550b5f1aba2
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/21-release-notes-2.4.0.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/21-release-notes-2.4.0.md

## File: claude-plugin/skills/caligrafy/reference/22-quickstart-with-claude.md
size: 7795
sha: 2ffa20d66190d83f7d1bbebf8f50163f845e451d
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/22-quickstart-with-claude.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/22-quickstart-with-claude.md

## File: claude-plugin/skills/caligrafy/reference/23-release-notes-2.5.0.md
size: 4415
sha: e0d31b6434f6eb9e841e8d19185ca89d8fdc3523
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/23-release-notes-2.5.0.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/23-release-notes-2.5.0.md

## File: claude-plugin/skills/caligrafy/reference/24-release-notes-2.6.0.md
size: 2859
sha: 06ec9caa0d43bae935d80197cea8c648db0913da
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/24-release-notes-2.6.0.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/24-release-notes-2.6.0.md

## File: claude-plugin/skills/caligrafy/reference/25-release-notes-2.7.0.md
size: 4229
sha: 58fa3d98339865cc4a9b4dc73289488823a733a9
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/25-release-notes-2.7.0.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/25-release-notes-2.7.0.md

## File: claude-plugin/skills/caligrafy/reference/26-design-styles.md
size: 5915
sha: d543aa6b0814a52b9cbec50ae3de62091129e4f2
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/26-design-styles.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/26-design-styles.md

## File: claude-plugin/skills/caligrafy/reference/27-release-notes-2.8.0.md
size: 2777
sha: 23087d8a37ebb118937053e40e3b5b589a8acb62
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/27-release-notes-2.8.0.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/27-release-notes-2.8.0.md

## File: claude-plugin/skills/caligrafy/reference/28-release-notes-2.9.0.md
size: 2487
sha: 591b2c2d12d00219fe3ceab624b70f31c0185d0d
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/28-release-notes-2.9.0.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/28-release-notes-2.9.0.md

## File: claude-plugin/skills/caligrafy/reference/29-release-notes-2.10.0.md
size: 1903
sha: 975d306b59430d44bb4ec5c69cef04240fb9264c
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/29-release-notes-2.10.0.md

````text
# Release notes: 2.10.0

> Source: Caligrafy Quill wiki - https://github.com/caligrafy/caligrafy-quill/wiki/Release-notes:-2.10.0
> Bundled with the caligrafy-quill Claude plugin. Content preserved from the wiki; formatting cleaned.

Caligrafy Quill **2.10.0** makes `upgrade` look for the latest release. Nothing breaks in existing projects. The full list is in the [changelog](https://github.com/caligrafy/caligrafy-quill/blob/master/CHANGELOG.md).

## `upgrade` checks for a newer release

```bash
php caligrafer.php upgrade            # a newer release? what moving to it changes (nothing is written)
php caligrafer.php upgrade --apply    # moves the project to it
```

`upgrade` used to compare your `application/` folder with the templates of the release your project already runs, so it never told you a newer release was out. Now it asks GitHub first:

+ **A newer release is out:** `upgrade` shows what moving to it changes, and `upgrade --apply` applies it exactly like [`update`](https://github.com/caligrafy/caligrafy-quill/wiki/Caligrafer#update): the framework, the release's other files you never changed, `composer.json`, `.gitignore`, the new `.env` settings, then the `application/` templates. Files you changed are kept, with the new version next to them as `<file>.caligrafy-new`.
+ **Your project runs the latest release:** `upgrade` compares `application/` with its templates, as before.
+ **Offline:** it says it could not check, and compares the templates.

`upgrade --local` skips the check. `update` still exists, and also takes a version: `update 2.10.0`.

## Upgrading from 2.9

```bash
php caligrafer.php update
```

From 2.10.0 on, `php caligrafer.php upgrade --apply` does it too.

The Claude Code plugin is 1.22.0: the `update` skill knows that `upgrade` finds newer releases (gotcha G96).

>[Back to the documentation](https://github.com/caligrafy/caligrafy-quill/wiki)
````

## File: claude-plugin/skills/caligrafy/reference/30-release-notes-2.11.0.md
size: 4465
sha: d5ac87b335cfa4524501c18927f9c948a4133728
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/30-release-notes-2.11.0.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/30-release-notes-2.11.0.md

## File: claude-plugin/skills/caligrafy/reference/31-release-notes-2.12.0.md
size: 1955
sha: f773943d0e057a78f45f5d203dc9e4e9f003e734
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/31-release-notes-2.12.0.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/31-release-notes-2.12.0.md

## File: claude-plugin/skills/caligrafy/reference/32-release-notes-2.13.0.md
size: 2360
sha: e92b8e67fd05585d08b253800b766056c326e561
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/32-release-notes-2.13.0.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/32-release-notes-2.13.0.md

## File: claude-plugin/skills/caligrafy/reference/33-release-notes-2.13.1.md
size: 1599
sha: d8076d35fdefc5a5337ece95e4018f61b1bb9708
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/33-release-notes-2.13.1.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/33-release-notes-2.13.1.md

## File: claude-plugin/skills/caligrafy/reference/34-release-notes-2.13.2.md
size: 2186
sha: 4f8d95cce4c69094a95d5d3d78cfe723c215de22
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/34-release-notes-2.13.2.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/34-release-notes-2.13.2.md

## File: claude-plugin/skills/caligrafy/reference/35-release-notes-2.13.3.md
size: 2054
sha: aafafac9532968e8056cf2a20b32fd72c2dd25d6
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/35-release-notes-2.13.3.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/35-release-notes-2.13.3.md

## File: claude-plugin/skills/caligrafy/reference/36-release-notes-2.13.4.md
size: 1635
sha: 1f2023d1a9dbada296a62b6a56f338304382b4d9
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/36-release-notes-2.13.4.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/36-release-notes-2.13.4.md

## File: claude-plugin/skills/caligrafy/reference/37-release-notes-2.13.5.md
size: 2077
sha: 4c23f07aa2394a29f7140b875430fbfa50a82328
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/37-release-notes-2.13.5.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/37-release-notes-2.13.5.md

## File: claude-plugin/skills/caligrafy/reference/38-release-notes-2.13.6.md
size: 1311
sha: 09d108a2257b465951032aca31abad3bff5ed614
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/38-release-notes-2.13.6.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/38-release-notes-2.13.6.md

## File: claude-plugin/skills/caligrafy/reference/39-scheduled-jobs.md
size: 8687
sha: a84e91684e0a52a79429546e0ddf99b093b90b68
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/39-scheduled-jobs.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/39-scheduled-jobs.md

## File: claude-plugin/skills/caligrafy/reference/40-release-notes-2.14.0.md
size: 2503
sha: 42075f398bd5d3aacc0696660f3d447d22b42e8c
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/40-release-notes-2.14.0.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/40-release-notes-2.14.0.md

## File: claude-plugin/skills/caligrafy/reference/41-release-notes-2.14.1.md
size: 1734
sha: c7b46976846f98d70cc51e18bc8d08cb700d7212
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/41-release-notes-2.14.1.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/41-release-notes-2.14.1.md

## File: claude-plugin/skills/caligrafy/reference/42-notifications.md
size: 8686
sha: b08465aff85b41c383092e048cd39b969e23b45e
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/42-notifications.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/42-notifications.md

## File: claude-plugin/skills/caligrafy/reference/43-release-notes-2.15.0.md
size: 2394
sha: 71f2545150e17483f7de4dae0a595aef7a1899e7
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/43-release-notes-2.15.0.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/43-release-notes-2.15.0.md

## File: claude-plugin/skills/caligrafy/reference/44-release-notes-2.16.0.md
size: 1603
sha: 3fa8796c231f1b77bc2d6537563ceafa68e7bef7
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/44-release-notes-2.16.0.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/44-release-notes-2.16.0.md

## File: claude-plugin/skills/caligrafy/reference/45-release-notes-2.16.1.md
size: 1636
sha: e71702f7fbc9aa4cd009d53592cc374250ade21c
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/45-release-notes-2.16.1.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/45-release-notes-2.16.1.md

## File: claude-plugin/skills/caligrafy/reference/api-reference.md
size: 81339
sha: 682546ac82b60774a8c2b87c33637262515fa837
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/api-reference.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/api-reference.md

## File: claude-plugin/skills/caligrafy/reference/gotchas.md
size: 80560
sha: c50d0cdb39c971024de2b15fade5cdb9b31740b3
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/gotchas.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/gotchas.md

## File: claude-plugin/skills/caligrafy/reference/recipes-ai-apps.md
size: 19634
sha: 958bf9d1c42485237941c5e54c4609a7203f3f63
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/recipes-ai-apps.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/recipes-ai-apps.md

## File: claude-plugin/skills/caligrafy/reference/recipes-face-login.md
size: 48049
sha: 70d47d5028d84f375ddecfb7ddf2ca9e7c58a775
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/recipes-face-login.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/recipes-face-login.md

## File: claude-plugin/skills/caligrafy/reference/ui-guidelines.md
size: 15195
sha: 49cb2438f7226c412fd3c4179f1a111f36bb88f2
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/ui-guidelines.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/ui-guidelines.md

## File: claude-plugin/skills/caligrafy/reference/wiki-sidebar.md
size: 8244
sha: 43158467f97ab773e4d0ae54c4fc8a640e808dac
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/wiki-sidebar.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/caligrafy/caligrafy-quill/blob/master/claude-plugin/skills/caligrafy/reference/wiki-sidebar.md

## File: README.md
upstream: https://github.com/caligrafy/caligrafy-quill/blob/master/README.md

````text
# Caligrafy - Describe it. Approve it. Ship it.
![Caligrafy: Describe it. Approve it. Ship it.](https://github.com/caligrafy/caligrafy-quill/blob/master/public/images/resources/describe-approve-ship-banner.png)

**Your idea turned into a complete app.** Describe it to [Claude Code](https://claude.com/claude-code), approve the
plan, and Caligrafy ships it online, with the code in your own GitHub repository.

<br/>

## Get started quickly

```bash
git clone https://github.com/caligrafy/caligrafy-quill.git myapp
cd myapp
claude
```
```
/caligrafy-quill:idea A reading list: I add books I want to read, mark them as read, and rate them from 1 to 5.
```

| | You | Claude and Caligrafy |
| --- | --- | --- |
| **1. Describe it** | your idea, in a sentence | a plan: the tables, every feature, who may do what, the pages |
| **2. Approve it** | read the plan, change it, say OK | build every feature once and check it |
| **3. Ship it** | approve the sign-ins in your browser | your GitHub repository, online at `https://<name>.caligrafy.com` |

Claude never asks for a password or a key, and asks before spending credits.
[Quickstart with Claude](https://github.com/caligrafy/caligrafy-quill/wiki/Quickstart-with-Claude) has the details.

**Start from a proven prompt** on the [marketplace](https://caligrafy.com/marketplace), and sell yours there once it is
online (`/caligrafy-quill:marketplace`, 25% of every sale).

<br/>

## A complete app

Every feature is written once, as a **capability** (its inputs, rules and permissions), and served everywhere:
**web pages**, **Vue apps** on desktop, tablet and phone, a **REST API**, and **AI agents** such as Claude or Cursor
over MCP. Same rules, same permissions, same audit log, whichever way it is called. A capability can also run on a
timer, with no page open ([scheduled jobs](https://github.com/caligrafy/caligrafy-quill/wiki/Scheduled-Jobs)), and
reach your users with the app closed ([notifications](https://github.com/caligrafy/caligrafy-quill/wiki/Notifications):
web push and email). Sign-in, tokens, payments, AI, design styles, a
[social card](https://github.com/caligrafy/caligrafy-quill/wiki/9.3-Metadata-&-Social-Rich-Cards) on every page and a
production `Dockerfile` come in the box. See
[How Caligrafy works](https://github.com/caligrafy/caligrafy-quill/wiki/How-Caligrafy-Works).

<br/>

## By hand

The same three steps without Claude, with Docker:
```bash
git clone https://github.com/caligrafy/caligrafy-quill.git myapp
cd myapp
.bin/caligrafer quickstart --example
```
Open http://localhost:8080/tasks, then follow the [Quickstart](https://github.com/caligrafy/caligrafy-quill/wiki/Quickstart) to use it over REST, from an AI agent,
and to ship it with `php caligrafer.php cloud deploy`.

<br/>

## Learn

| If you want to | Read |
| --- | --- |
| See everything Caligrafy does | [The documentation](https://github.com/caligrafy/caligrafy-quill/wiki/) |
| Learn step by step | [The Caligrafy course](https://github.com/caligrafy/caligrafy-quill-course) |
| Build a feature | [Your first capability](https://github.com/caligrafy/caligrafy-quill/wiki/Your-First-Capability) |
| Put it online | [Putting it online](https://github.com/caligrafy/caligrafy-quill/wiki/Putting-it-Online): Caligrafy Cloud or your own server |
| See what changed | [Changelog](CHANGELOG.md) |
| Contribute | [Contributing](CONTRIBUTING.md) and [how the framework is built](ARCHITECTURE.md) |

<br/>

## Community and support

+ **GitHub:** [issues](https://github.com/caligrafy/caligrafy-quill/issues) for bugs and feature requests.
+ **Slack:** [join the Caligrafy community](https://join.slack.com/t/caligrafy/shared_invite/enQtNzI4MDY2OTA4MTgzLTI2NDc2NTVmMDNlMWQ5YWYxN2RjZTkwZjdiNjM5ZTg3NjQ2YWYyMzRmZDgzNWE0Nzc4YjQyODM2NDNkNjQ2OTU) to get help and share what you build.
+ **Facebook:** the [Caligrafy group](https://www.facebook.com/groups/caligrafy/).
+ **Videos:** the [Caligrafy channel](https://www.youtube.com/playlist?list=PLsJG81fLk3_VWmXl9DnEldYY9ei4jTCjA).

If Caligrafy helps you, [sponsor it on GitHub](https://github.com/sponsors/DoryAzar) or share
[caligrafy.com](https://caligrafy.com).

<br/>

Caligrafy is released under the [MIT License](LICENSE).
````
