Download the PHP package 1994/ghostwriter-core without Composer

On this page you can find all versions of the php package 1994/ghostwriter-core. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package ghostwriter-core

Ghostwriter Core

The framework-free core shared by the Ghostwriter addons for Statamic, Filament and Craft CMS. It holds the parts that used to be copied by hand between the three: draft text handling, the prompts, the connection to the AI providers, the schema model and the layout algorithms, the domain model and its rules, and photo search with ranking.

Each addon stays a thin adapter. It reads the CMS's schema and entries, stores state, runs queue jobs, checks permissions and draws the UI, and calls core for everything else.

Core depends on no framework or CMS. CI fails if src/ names Illuminate, Laravel, Craft, Yii, Statamic, Filament or Livewire (bin/check-boundaries).

Install

PHP 8.2 or later, with dom and mbstring. Runtime dependencies are symfony/yaml (6.4, 7 or 8), league/commonmark 2 and the PSR HTTP and log interfaces (psr/log 1 to 3). guzzlehttp/guzzle (7.8+ or 8) is suggested, not required: it's needed for Http\GuzzleHttpClients, the ready-made HTTP client, and for Testing\MockHttpClient's default factories. CI runs the lowest and highest versions allowed, and Guzzle 7 and 8 each.

Core follows semantic versioning from 1.0.0. The addons require ^1.0.

What's in it

Text: NineteenNinetyFour\Ghostwriter\Core\Text

Class What it does
Draft Parses a YAML draft (rich text as markdown, blocks as a list). Unwraps code fences and explains what's wrong with a bad one.
LenientYaml Reads YAML written by a model, re-quoting prose that strict YAML would choke on.
TaggedResponse Splits a <reply>…</reply><draft>…</draft> answer, and the writer's optional <images> block.
HtmlToMarkdown Turns rich text HTML into the markdown a writer would type.
DraftPreview Lays a draft out as a tree for the panel, with safe HTML and a path for each editable piece.
EntryMerger Lays a rewritten draft over the entry it came from, keeping block IDs and everything that isn't writing.
EntrySimplifier Reduces a stored entry to draft form, for showing real entries to the model as examples.
Utf8 Utf8::scrub() makes every string in a value valid UTF-8 before it's encoded as JSON.
Slug Slug::make() for file names (ASCII, accents taken off, cut between words) and Slug::clip() for one-line titles and alt text.

Where the three addons' copies differed, the difference is a constructor option. See docs/text-unification.md for what each addon passes.

Prompts: NineteenNinetyFour\Ghostwriter\Core\Prompts

The twelve prompts live in resources/prompts. The CMS's own words (website or app, section or resource, entry or record) are [[name]] placeholders, filled from a Vocabulary:

AI: NineteenNinetyFour\Ghostwriter\Core\Ai

One way to call a model, used by all three addons, over any PSR-18 client. It covers Anthropic, OpenAI, Gemini and OpenRouter for text, and OpenAI, Gemini and OpenRouter for images. See docs/providers.md.

Studio: NineteenNinetyFour\Ghostwriter\Core\Studio

Every model call the addons make for writing, planning and learning a site: the voice guide, the type analysis (with its re-ask), kind suggestions, plan ideas, the imagery guide, brief drafts and the writer's turns. Inputs are small value objects (VoiceSample, TypeSurvey, KindSurvey, PlanContext, ImagerySample, Conversation, WriterContext), never CMS objects; results are core types with their token usage. The cut-off policy and the logging of unreadable replies live here, once. See docs/studio-unification.md for what each addon passes.

Schema and layouts: NineteenNinetyFour\Ghostwriter\Core\Schema and …\Core\Layout

What a kind of entry is made of (Schema, Field, Kind, Set) and an entry's content in one shape whatever the CMS (EntryData), and the algorithms that work on them: SchemaDescriber (the fields as the model's brief), PatternFinder (how a group's entries are really built: block order and usage, house defaults, examples, fill rates), KindFinder (kinds of entry, from how entries are built, with no model call), HouseStyle (settings, links, nested items and rich text dressing agreed place by place) and EntryBuilder (a draft into entry data). How a CMS stores rich text and links is a dialect: HtmlDialect for HTML, a Bard dialect in the Statamic adapter, and StatamicLinks, CraftLinks and NoLinks. See docs/layout-unification.md for what each addon passes.

Anchoring and the page preview: …\Core\Anchor, …\Core\Arrange and …\Core\Preview

What page preview's comments and Suggest edits share: quotes of a range of text and where they are now (Anchor\TextQuote, QuoteFinder), the checks every scoped edit passes (SourceCheck, ScopedEditCheck), stable ids for every piece of draft text, carried from turn to turn (Arrange\Units, UnitMatcher), and the preview's invisible markers (Preview\PreviewMarkers) with the framework-free locator that maps a rendered page back to its blocks (resources/js/preview/locator.js, copied into each addon). Nothing here calls a model. See docs/preview.md.

Comments on the page (…\Core\Review): threads anchored to units, shared on the session and following their words from layout to layout. See docs/comments.md.

Suggest edits and Content to revisit: …\Core\Suggest and …\Core\Revisit

Ghostwriter reviews an existing entry and suggests small, anchored edits, and ranks the site's pages that need a look. The free checks find past years written as current, passed closing dates, counts to recheck, long sentences, empty link text, overlaps, broken links, missing alt text and SEO length. They cost nothing, in English, German, French, Dutch and Spanish (Suggest\Findings). The Content to revisit index is built from them alone (Revisit\RevisitIndex), with an opt-in weekly check of links to other sites. The review call (Studio::suggestEdits(), split into several calls for a long page) writes the fixes and adds judgement. Every suggestion is anchored with Anchor\TextQuote, and any that adds a fact is dropped. Reviews are shared, and their decisions are kept as the page's history (Suggest\EditReviews). See docs/suggest-edits.md.

Images: NineteenNinetyFour\Ghostwriter\Core\Images

Photo search for an image field: free photo libraries searched, the results judged by a model against the page, and the chosen file downloaded safely. Each addon supplies the words around the field, the images already in that place (if any) and storage for the photo that's chosen. See docs/images.md for the wiring.

Class What it does
StockSearch Searches Unsplash, Pexels and Pixabay (with a key from Credentials: unsplash, pexels, pixabay) and Openverse (no key; CC0 and public domain only; can be switched off). search($term, $shape) gives Photos; fetch($source, $id) looks the photo up again and downloads it as a PhotoFile. It holds a set of Libraries\PhotoLibrary objects (the four free ones in Libraries\Free, plus any passed as libraries); search(..., sources:) keeps a search to some of them.
Libraries\PhotoLibrary One photo library: search(SearchQuery), photo($id), fetch($id), and its Capabilities (free or paid, how costs are quoted, how long a comp may be kept, mayRank: whether a model may see its photos, false for paid libraries; and noModelInput). Offer and Cost say how a paid photo can be had; Preview is a paid library's comp. A paid library is a LicensableLibrary (quotes(), license(), download(), findLicences()); Testing\FakeLibrary is a scripted one for tests and demos.
PhotoFinder The whole job: find(PhotoContext, $references, ?$terms) chooses searches (the photo-researcher agent, or the person's own), runs them, has the results judged, and runs a second round when nothing fits. Returns PhotoResults.
PhotoRanker The judging, through the photo-picker agent. With reference images it matches style and subject; without, subject alone. Clear misses are left out. Without a model, results come back unranked and nothing is picked. Photos from a library without mayRank are never shown to the model: they follow the judged ones, unjudged.
Photo One result: source, id, thumbnail, credit, licence, the library's own title, description and tags, size, the search that found it, and picked/reason when a model judged it. alt(), assetTitle() and filenameBase() turn the library's words into alt text, an asset title and a file name, falling back to the search term.
PhotoContext The words around the field: page title and summary, field label, block text, page text, shape, and the site's imagery guide.
PhotoResults The photos in order, with judged, withReferences, noneFit, retried and the terms searched. picked() is the shortlist; it is empty unless a model judged, so a "Best match" badge can follow picked alone.

Downloads are https only, redirects included (core follows them itself, at most three, and never to a private address), read no further than their cap (15 MB for a photo, 2 MB for a thumbnail) and checked to be a JPEG, PNG or WebP. Openverse results are kept only when Openverse's own thumbnail loads; originals are never fetched to check them.

Domain: NineteenNinetyFour\Ghostwriter\Core\Domain

The pieces being written (Sessions\Session), the content plan (Planning\Idea, PlanState), kinds of content (Kinds\ContentType, KindSuggestions), the voice and image style guides (Guides\Guide, GuideState), image requests (Images\ImageRequest) and the queue-waiting notice (Queue\Waiting), with their rules: who may see, resume and delete a piece (SessionAccess), one run at a time under a per-session lock with stale runs recovered (SessionGuard), when a piece is finished (Progress), and the plan's review, dismissal and put-back rules (Plan). Since 1.1 it also holds the stock image ledger (Stock\StockImage, StockImages: every stock image put into the site, its licence state and where it is used) and the Stock\ModelInputGuard that keeps Getty and iStock images out of every model call. Each addon implements the store interfaces (SessionStore, PlanStore, KindStore, GuideStore, ImageRequestStore, WaitingStore, and from 1.1 StockImageStore) and the Lock port, and proves them with the contract tests in tests/Contracts. Every type reads and writes the shape the addon stores today (fromArray($stored, Format::Craft)), so no data migration is needed. In-memory stores for tests are in Domain\Testing. See docs/domain-unification.md for what differed.

Images\Placeholders draws the striped placeholder and decides where it goes (D10); the addon saves the file through an AssetSink.

Wiring it into an addon

The adapter implements three ports and builds one Providers, shared for the whole request or process:

Port What it answers Laravel addons Craft
Ports\Credentials key(provider): the trimmed key, or null. Env names are in Credentials::ENV. config("ghostwriter.keys.{$provider}") App::env(Credentials::ENV[$provider])
Ports\HttpClients A PSR-18 client for a timeout, plus PSR-17 factories new GuzzleHttpClients() Craft::createGuzzleClient([...]) wrapped in a small class, or new GuzzleHttpClients(['handler' => $stack])
Ports\ProviderSettings Text and image provider and model, timeout, base URLs, Anthropic fallbacks on or off from config('ghostwriter.*') or the settings model the plugin's Settings

Photo search uses the same HttpClients. An HttpClients of your own (Craft's wrapper) should also implement Ports\DownloadClients, a client that hands redirects back instead of following them, so core can check each hop is https. GuzzleHttpClients and MockHttpClient already do.

ArrayCredentials and StaticProviderSettings are plain implementations, for tests or for hosts that read their config once.

Logging is optional: pass any PSR-3 logger. A finished call is logged at info, each retry at warning and a failed call at error. Prompts, replies, images and keys are never logged.

Retries can make a call take up to timeout × 3 plus the waits, so queue jobs should allow timeout × 3 + 60 seconds.

How the registry waits between retries, and how often it retries, are constructor arguments (sleeper:, retry:). To change them on a registry that's already built, withSleeper(Sleeper) and withRetryPolicy(RetryPolicy) return a configured copy; the registry is otherwise immutable, so rebind the copy wherever the original was shared. A fake standing in carries over to the copy.

Request limits

Ai\Limits::MAX_IMAGES (24) and Ai\Limits::MAX_IMAGE_BYTES (20 MB of raw image data) cap one request, and every provider refuses a request over them before sending it. Limits::fits($images) checks a list in advance. 24 leaves room for the photo picker's 3 references and 18 thumbnails. 20 MB of raw bytes is about 27 MB once base64-encoded, which is under Anthropic's 32 MB request limit.

Testing with FakeProvider

When PHPUnit is loaded (Pest included), the asserts go through PHPUnit\Framework\Assert, so each one counts as an assertion and a test that only asserts on the fake isn't marked risky. Without PHPUnit they throw AssertionError, so core needs no test framework at runtime.

Testing without keys

A fake marked unconfigured makes the registry behave as if the keys were missing, so the no-key paths can be tested while nothing leaves the machine:

reset() keeps whether the fake is unconfigured.

To test at the HTTP level, use Testing\MockHttpClient. It is both a PSR-18 client and an HttpClients, it records every request, and it can throw Testing\NetworkError::connectFailed() or ::timedOut(). Pass Testing\RecordingSleeper to Providers (or use withSleeper()) so retries record their waits instead of sleeping.

Development

The addons' documentation screenshots come from one re-runnable tool in tools/screenshots/. It drives each addon's local test site, sets up each scene without model calls, and restores the site afterwards. See its README. The tools/ folder isn't included in the Composer package.

To work on core and an addon together, point the addon at a sibling checkout with a Composer path repository in a gitignored composer.local.json, so it never reaches a release.

Licence

Proprietary. See LICENSE.


All versions of ghostwriter-core with dependencies

PHP Build Version
Package Version
Requires php Version ^8.2
ext-mbstring Version *
ext-dom Version *
league/commonmark Version ^2.4
psr/http-client Version ^1.0
psr/http-factory Version ^1.1
psr/http-message Version ^1.1|^2.0
psr/log Version ^1.1|^2.0|^3.0
symfony/yaml Version ^6.4|^7.0|^8.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package 1994/ghostwriter-core contains the following files

Loading the files please wait ...