PHPackages                             xuple/evolayer-base - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. xuple/evolayer-base

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

xuple/evolayer-base
===================

EvoLayer Base — AI agents, ontology, and premium React blocks for the Laravel React Inertia starter. Part of the EvoDevOps starter-kit family.

v0.1.0(yesterday)05↑2900%MITPHPPHP ^8.3CI passing

Since Jun 9Pushed todayCompare

[ Source](https://github.com/xuple/evolayer-base)[ Packagist](https://packagist.org/packages/xuple/evolayer-base)[ RSS](/packages/xuple-evolayer-base/feed)WikiDiscussions main Synced today

READMEChangelog (1)Dependencies (12)Versions (2)Used By (0)

EvoLayer Base
=============

[](#evolayer-base)

EvoLayer Base is a Composer package that adds an AI / ontology / React-block substrate to a Laravel 13 + React + Inertia application. It is designed to feel like a clean additive layer for a developer transitioning from `laravel/react-starter-kit`. **Installing it adds zero routes and no active request surface by default.** Shared props and feature pages are host opt-in via flags and publish tags.

EvoLayer Base is also the intended foundation for future EvoLayer sibling packages — **Commerce** (product sales), **SaaS** (subscriptions / tenants), and **RLS** (PostgreSQL row-level security) — but those layers ship as separate packages and are not part of this one. EvoDevOps is the teaching/site brand for the family.

Public web strategy: `evodevops.com` is the editorial/teaching home for the starter-kit family, while `https://docs.evodevops.com/base` is the canonical EvoLayer Base documentation path. The starter's public `/` page mounts the EvoLayer Base demo/install explainer; when marketing pages are enabled, the package also exposes that page at `/about`.

Start here
----------

[](#start-here)

If you are...UseStarting a new application[`xuple/evolayer-base-starter`](https://github.com/xuple/evolayer-base-starter) — the Laravel React starter with EvoLayer already wired inAdding EvoLayer to an existing Laravel React appThis package (`xuple/evolayer-base`)Building a sibling packageThe contracts in [Invariant contracts](#invariant-contracts-for-variant-authors)What you get
------------

[](#what-you-get)

- Structured AI streaming built on `laravel/ai`, with ThreadStudio, PRD generation, text assist, `evolayer:ai:smoke-test`, `evolayer:ai:probe`, and `evolayer:ai:stream-check`.
- A small ontology compiler and TypeScript contract generator for describing app entities, routes, and blocks.
- React blocks and example pages that publish into the host app without taking over the starter.
- Admin and lineage primitives (`AdminGate`, `ChangeEvent`, AI invocation ledger) intended for reuse by Commerce, SaaS, and RLS packages.

---

Quick start
-----------

[](#quick-start)

```
# 1. Install the package
composer require xuple/evolayer-base

# 2. Publish the always-on bits: config, core frontend, patches, npm deps
php artisan vendor:publish --tag=evolayer-base-config
php artisan vendor:publish --tag=evolayer-base-frontend-core
php artisan vendor:publish --tag=evolayer-base-patches
php artisan vendor:publish --tag=evolayer-base-npm

# 3. Install the npm deps that the published frontend needs
# (cmdk powers the standard command palette — required if you use the
# published frontend at all)
npm install cmdk

# 4. Run package migrations
php artisan migrate
```

EvoLayer migrations auto-load from the package via Laravel's package migration loader. Do **not** publish them by default; `vendor:publish --tag=evolayer-base-migrations` is only for hosts that intentionally want to own and customise the schema.

After step 4 the package is installed but **does nothing yet** — `php artisan route:list` shows no new routes. You opt in to features via env flags + per-feature publish tags (see below).

EvoLayer Base targets the Laravel React starter shape. Existing Inertia apps that have renamed routes, layouts, components, or aliases may need adapter edits after publishing frontend stubs.

For a one-shot install into an existing app, use:

```
php artisan evolayer:install
```

Then run `php artisan evolayer:doctor` to verify the package bindings, ontology cache, and AI patch state from the CLI runtime. `doctor` does not prove that a separate web-server or PHP-FPM user can write Laravel cache or storage paths; hosted filesystem permissions belong to the host application or starter deployment docs.

### Enabling a feature (two coupled steps)

[](#enabling-a-feature-two-coupled-steps)

Each opt-in feature is two things that must match: an **env flag** (registers its routes) and a **frontend publish tag** (drops its pages). Do both together — a published page whose route isn't registered will fail `tsc`, and an enabled route with no page is a dead link.

```
# Example: enable ThreadStudio
echo 'EVOLAYER_BASE_EXAMPLE_THREAD_STUDIO=true' >> .env
php artisan vendor:publish --tag=evolayer-base-frontend-thread-studio
php artisan wayfinder:generate --with-form
```

FeatureEnv flagFrontend publish tagThreadStudio`EVOLAYER_BASE_EXAMPLE_THREAD_STUDIO``evolayer-base-frontend-thread-studio`PRD Studio`EVOLAYER_BASE_EXAMPLE_PRD_STUDIO``evolayer-base-frontend-prd-studio`Admin Inbox`EVOLAYER_BASE_EXAMPLE_ADMIN_INBOX``evolayer-base-frontend-admin-inbox`Contact + AI`EVOLAYER_BASE_EXAMPLE_CONTACT_AI``evolayer-base-frontend-contact-ai`Marketing pages`EVOLAYER_BASE_EXAMPLE_MARKETING_PAGES``evolayer-base-frontend-marketing-pages`Voice input`EVOLAYER_BASE_EXAMPLE_VOICE_INPUT`(block ships in core; no page tag)AI text field`EVOLAYER_BASE_EXAMPLE_AI_TEXT_FIELD`(block ships in core; no page tag)`voice_input` and `ai_text_field` are blocks consumed by other pages (ThreadStudio, PRD), not standalone pages. Enabling their flags registers their endpoints; the consuming page receives the endpoint URL as a server prop and shows the relevant control only when the flag is on — so no cross-feature compile-time dependency exists.

To publish everything at once (demo / kitchen-sink), use the meta tag `evolayer-base-frontend` and enable all flags.

---

Standard features (installed once, always on)
---------------------------------------------

[](#standard-features-installed-once-always-on)

These ship as part of the package's frontend stubs and don't need a flag. They're "table-stakes" UX that every starter should have.

FeatureWhat it gives you**Command palette (⌘K)**`cmdk`-powered fast nav and feature discovery; published to `resources/js/components/command-bar.tsx` and `command-palette-dialog.tsx`. Requires `cmdk` (installed at step 3 above).**Block primitives**`streaming-card`, `ai-triage`, `ai-text-field`, `voice-input`, `semantic-search` — composable React blocks under `resources/js/blocks/`.**Type contracts**`EvoLayerSharedProps`, `EvoLayerExamples`, `EvoLayerFeatures`, `EvoLayerNavItem` published to `resources/js/types/evolayer.d.ts`.**`useEvoLayerProps()` hook**Type-safe access to the `evolayer` shared prop. Pages call this instead of destructuring `usePage().props` directly.---

Opt-in features (enable per env flag)
-------------------------------------

[](#opt-in-features-enable-per-env-flag)

Every feature defaults to **off**. Set the corresponding env flag to `true` to enable. Each flag also gates the route file for that feature — the routes only appear in `route:list` when their flag is on.

Env flagWhat it enables`EVOLAYER_BASE_EXAMPLE_THREAD_STUDIO=true``/ai/thread-studio` — AI customer-reply composer with structured streaming`EVOLAYER_BASE_EXAMPLE_PRD_STUDIO=true``/admin/prd` — AI-assisted PRD generator`EVOLAYER_BASE_EXAMPLE_ADMIN_INBOX=true``/admin/inbox` + `/admin/submissions` — form-submission inbox UI`EVOLAYER_BASE_EXAMPLE_CONTACT_AI=true``/contact` + AI subject hints + AI triage on submission`EVOLAYER_BASE_EXAMPLE_VOICE_INPUT=true``/ai/voice-input/transcribe` — speech-to-text endpoint for the `` block`EVOLAYER_BASE_EXAMPLE_AI_TEXT_FIELD=true``/ai/text-assist/stream` — text-suggestion streaming endpoint for the `` block`EVOLAYER_BASE_EXAMPLE_MARKETING_PAGES=true``/about` + `/home` — showcase landing pages mapped to the published `evolayer/about.tsx` and `evolayer/home.tsx``EVOLAYER_BASE_FEATURE_CONTACT_ATTACHMENTS=true`File-upload handling on the contact form. Requires `composer require spatie/laravel-medialibrary` (see "Opt-in extras" below)After enabling, run `php artisan route:list` to confirm only the routes you asked for are registered.

---

Opt-in extras (composer packages)
---------------------------------

[](#opt-in-extras-composer-packages)

Base requires `spatie/laravel-permission` and `spatie/laravel-activitylog` as core dependencies. The other two Spatie packages are optional and only required when you enable the related features:

Spatie packageFeature it enablesRequired when`spatie/laravel-medialibrary`Contact form file attachments + AI media analysis`EVOLAYER_BASE_FEATURE_CONTACT_ATTACHMENTS=true``spatie/laravel-tags`AI auto-tagging on form submissions during triageOptional with `EVOLAYER_BASE_EXAMPLE_CONTACT_AI=true`; triage still stores urgency/sentiment/summary when absent and records `tags_skipped=true`To enable contact attachments:

```
composer require spatie/laravel-medialibrary
php artisan vendor:publish --provider="Spatie\\MediaLibrary\\MediaLibraryServiceProvider" --tag="medialibrary-migrations"
php artisan migrate
```

Set `EVOLAYER_BASE_FEATURE_CONTACT_ATTACHMENTS=true` in `.env`. The package's `FormSubmission` model loads either way — a compat polyfill in `Xuple\EvoLayer\Base\Compat\*` shadows the Spatie interfaces and traits when the Spatie packages aren't installed.

If you use Spatie activitylog, tags, or medialibrary with EvoLayer ULID models, make the related morph id columns ULID/string-compatible before migrating on PostgreSQL:

- `activity_log.subject_id` must support ULID subjects (`nullableUlidMorphs('subject', 'subject')`).
- `taggables.taggable_id` must support ULID taggables (`ulidMorphs('taggable')`).
- `media.model_id` must support ULID media owners (`ulidMorphs('model')`).
- `activity_log.causer_id` may stay integer when causers are integer-keyed users.

The EvoLayer Base starter commits corrected Spatie migrations for this. Package-only installs that publish upstream Spatie migrations must make the same edits before running `migrate` against PostgreSQL.

---

Host integration steps
----------------------

[](#host-integration-steps)

A handful of host-owned files need small edits the package cannot publish over. They are small and stable.

If you start from [`xuple/evolayer-base-starter`](https://github.com/xuple/evolayer-base-starter), these edits are already applied.

### 1. Apply the `laravel/ai` patch

[](#1-apply-the-laravelai-patch)

Until upstream lifts the structured-output streaming guard, structured streaming requires patching `vendor/laravel/ai/src/Providers/Concerns/StreamsText.php`. The patch ships at `patches/laravel-ai-structured-streaming.patch` after `vendor:publish --tag=evolayer-base-patches`:

```
patch -p1 -d vendor/laravel/ai --forward  {
    const pages = import.meta.glob('./pages/**/*.tsx', { eager: true });
    const page = pages[`./pages/${name}.tsx`];

    if (name.startsWith('evolayer/') && !name.startsWith('evolayer/admin/') && name !== 'evolayer/home') {
      page.default.layout = page.default.layout ?? ((p: ReactElement) => {p});
    }

    return page;
  },
  // ...
});
```

### 4. Surface EvoLayer nav entries in your sidebar

[](#4-surface-evolayer-nav-entries-in-your-sidebar)

In `resources/js/components/app-sidebar.tsx`:

```
import { useExampleNavItems } from '@/hooks/use-example-nav-items';
import { sidebarPrimaryNavItems } from '@/config/navigation';

export function AppSidebar() {
  const evoItems = useExampleNavItems(sidebarPrimaryNavItems);
  const navMain = [...yourOwnItems, ...evoItems];
  // ...
}
```

`useExampleNavItems()` filters items by the `EVOLAYER_BASE_EXAMPLE_*` flags so disabled features don't appear.

---

Invariant contracts (for variant authors)
-----------------------------------------

[](#invariant-contracts-for-variant-authors)

These are the stable surfaces sibling EvoLayer packages (Commerce, SaaS, RLS) and host apps can depend on. Breaking changes are versioned.

### Namespacing convention

[](#namespacing-convention)

SurfaceConventionRoute names`evolayer.base.*` (variants use `evolayer.commerce.*`, etc.)Env vars`EVOLAYER_BASE_EXAMPLE_*`, `EVOLAYER_BASE_FEATURE_*` (variants use `EVOLAYER_COMMERCE_*`, etc.)Shared props`evolayer.base.{examples, features}` (variants own `evolayer.commerce.*`, etc.)Config`config/evolayer.php`; access via `evolayer.base.*`Publish tags`evolayer-base-*` (variants use `evolayer-commerce-*`, etc.)Migration filenamesInclude `evolayer_base_` segment (variants include `evolayer_commerce_`, etc.)### SSE streaming vocabulary

[](#sse-streaming-vocabulary)

Stable event names emitted by Base streaming endpoints:

EventPayload`start``{}``field_delta``{name, delta}` (structured streaming per-field accumulation)`field_complete``{name}``text_delta``{delta}` (plain text streaming)`done``{result?, invocation_id?, duration_ms?, text?}``error``{message, failure_type, provider?, model?, missing_fields?, invalid_fields?}`Reserved for future Base / variant use (documented now to prevent collision): `tool_call`, `tool_result`, `thinking_delta`, `usage`.

### Toast / flash contract

[](#toast--flash-contract)

```
use Xuple\EvoLayer\Base\Support\Toast;
Toast::success('Done.');
// or directly:
Inertia::flash('toast', ['type' => 'success', 'message' => 'Done.']);
```

Payload shape: `{type: 'success'|'error'|'warning'|'info', message: string, ...}`.

### Schema invariants

[](#schema-invariants)

The following columns are guaranteed on Base's prefixed tables/models for variant/RLS compatibility:

- `evolayer_base_ai_invocations` (`AiInvocation`): nullable `subject_type`/`subject_id` polymorph, `tenant_id`, `prompt_tokens`, `completion_tokens`, `cost_cents`, `cost_currency`.
- `evolayer_base_change_events` (`ChangeEvent`): nullable string-compatible polymorphic `actor_type`/`actor_id` (User by default; variants may record Customer/Tenant/System), nullable `tenant_id`.

### `AdminGate` contract

[](#admingate-contract)

```
interface AdminGate {
    public function isAdmin(?Authenticatable $user): bool;
    public function can(?Authenticatable $user, string $ability, mixed $resource = null): bool;
}
```

Default impl (`SpatieAdminGate`) routes `evolayer.admin` through `hasRole('admin')` and arbitrary abilities through Laravel's `Gate` facade. Replace via container binding to plug in a different model.

### Ontology multi-file architecture

[](#ontology-multi-file-architecture)

The package's `OntologyCompiler` supports merging multiple ontology files keyed by namespace. Variant packages register their `ontology.yaml` during boot:

```
// In a variant package's ServiceProvider::boot()
app(\Xuple\EvoLayer\Base\Support\OntologyRegistry::class)
    ->register('evolayer.commerce', __DIR__.'/../ontology.yaml');
```

The compiler reads the registry plus the host's own `ontology.yaml` and produces a namespace-keyed merged output.

---

Pluggable admin gate
--------------------

[](#pluggable-admin-gate)

The package ships an `AdminGate` contract with a default `SpatieAdminGate` implementation (`hasRole('admin')`). To plug in a different authorisation model, bind your own implementation in `AppServiceProvider`:

```
$this->app->singleton(\Xuple\EvoLayer\Base\Contracts\AdminGate::class, MyAdminGate::class);
```

---

Known constraints for 0.1
-------------------------

[](#known-constraints-for-01)

- Assumes `laravel/fortify` for authentication when using the default `SpatieAdminGate`. Other auth setups require a custom `AdminGate` binding.
- Assumes the host `users` table uses the default Laravel convention (integer PK, table name `users`).
- Structured-output streaming requires the manual `laravel/ai` patch above until upstream lands the fix. Tracked in `patches/README.md`.
- The package does not automatically declare its patch in the host's composer.json — the `cweagans/composer-patches` plugin v2 cannot resolve dependency-relative patch paths at install time. The starter template (or your `composer.json`) handles this.

---

Development
-----------

[](#development)

```
composer install
composer test
composer validate --strict
```

`composer install` automatically applies the `laravel/ai` patch to the package's own vendor copy so the test suite has structured streaming available.

The package's own test suite runs against `require-dev` Spatie packages (medialibrary, tags) — meaningful "no Spatie installed" CI verification needs a separate composer install without dev deps. The compat layer's no-op stubs are independently asserted in `tests/Unit/CompatNoopStubsTest.php`.

Support and project status
--------------------------

[](#support-and-project-status)

EvoLayer is pre-1.0. Breaking changes are expected until the first tagged release. See [CHANGELOG.md](CHANGELOG.md) for changes, [RELEASE.md](RELEASE.md) for the current release process, and [DECISIONS.md](DECISIONS.md) for durable architecture decisions.

License
-------

[](#license)

MIT. See [LICENSE](LICENSE).

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance100

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

1d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/b471eca879b57abd5c570576cc45cf574f9c118535a119b1914cb02b46a61fe6?d=identicon)[xuple](/maintainers/xuple)

---

Top Contributors

[![xuple](https://avatars.githubusercontent.com/u/140922384?v=4)](https://github.com/xuple "xuple (91 commits)")

---

Tags

laravelaiinertiareactontologyevolayerevodevopsxuple

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/xuple-evolayer-base/health.svg)

```
[![Health](https://phpackages.com/badges/xuple-evolayer-base/health.svg)](https://phpackages.com/packages/xuple-evolayer-base)
```

###  Alternatives

[statamic/cms

The Statamic CMS Core Package

4.8k3.5M901](/packages/statamic-cms)[nasirkhan/laravel-starter

A CMS like modular Laravel starter project.

1.4k2.7k](/packages/nasirkhan-laravel-starter)[unopim/unopim

UnoPim Laravel PIM

10.1k2.2k](/packages/unopim-unopim)[emargareten/inertia-modal

Inertia Modal is a Laravel package that lets you implement backend-driven modal dialogs for Inertia apps.

90128.1k](/packages/emargareten-inertia-modal)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
