Command reference
Every Artisan command in streams/sdk: what it writes, its arguments and options, JSON output for agents, and the command classes that are not registered.
streams/sdk adds Artisan commands to generate and check streams, entries, addons, schemas, and Livewire components. Install it as a dev dependency:
composer require --dev streams/sdk:1.0.x-dev
Commands are registered only when the app runs in the console. Run php artisan list to see what your installed version provides.
Available commands
| Command | What it does |
|---|---|
make:stream |
Writes a validated streams/{id}.json |
make:entry |
Creates or updates an entry in the stream's source |
make:addon |
Scaffolds an addon package in addons/{vendor}/{name}/ |
streams:list |
Lists registered streams, as a table or JSON |
streams:validate |
Checks definitions against the JSON Schema and the running app |
streams:schema |
Writes a {id}.schema.json entry schema per stream |
streams:livewire |
Generates Livewire 3 index, form, and show components |
Agents can call the same operations through the MCP server (php artisan mcp:start streams), which needs laravel/mcp and Laravel 11.45+ or 12.41+.
make:stream
php artisan make:stream {id} {--name=} {--description=} {--force}
Writes streams/{id}.json. The ID must be snake_case (lowercase letters, numbers, and underscores, starting with a letter), and it is also the file name. --name defaults to the ID word-cased (blog_posts becomes "Blog Posts"). The file sets config.source.format to json without a source.type, so the app's default adapter is used, and it has one id field of type uuid with config.default: true.
The definition is validated (the same checks as streams:validate) before it is written, then registered. The command refuses to replace an existing file unless you pass --force, and it refuses an ID that the app or an addon already registers.
php artisan make:stream blog_posts --description="Articles on the blog."
# Stream created: streams/blog_posts.json
The file starts with "$schema": "https://streams.dev/schema/streams.schema.json". That URL is served by this site at /schema/streams.schema.json, so editors that understand $schema validate and autocomplete the file.
make:entry
php artisan make:entry {stream} {input?} {--update}
Creates an entry in {stream}. input is query-string formatted (title=Hello&status=draft). The command prompts for every field you didn't provide, validates the input against the stream's rules, then saves. With --update, validation treats the entry as existing, and if the input includes the stream's key (key_name, default id) and that entry exists, its attributes are updated.
php artisan make:entry posts "title=Hello&status=draft"
php artisan make:entry posts "id=hello&status=live" --update
Validation errors are printed and nothing is saved. On success the saved entry is printed as JSON.
make:addon
php artisan make:addon {vendor/name} {--description=} {--force}
Scaffolds a Composer package under addons/{vendor}/{name}/ with a composer.json and a service provider at src/{Name}Provider.php. The name must be a valid Composer package name. Without --description the command asks for one. It refuses to overwrite an existing addon unless you pass --force.
php artisan make:addon acme/reviews --description="Product reviews."
# Created: addons/acme/reviews/composer.json
# Created: addons/acme/reviews/src/ReviewsProvider.php
Add the directory as a Composer path repository to install it. See Addons.
streams:list
php artisan streams:list {--json}
Lists every registered stream, sorted by ID, with its name, source type, field count, and description. --json prints an array instead, which is what scripts and agents should read:
[
{
"id": "posts",
"name": "Posts",
"description": "Blog posts.",
"source": "filebase",
"fields": ["id", "title", "status", "author_id"],
"extends": null
}
]
streams:validate
php artisan streams:validate {paths?*} {--json}
Validates stream definitions. With no paths it checks every streams/*.json. Each file is checked in three steps, and later steps run only when earlier ones pass:
- The stream definition JSON Schema (the same file
$schemapoints at). - The running app: field types must be registered,
extendsmust name a registered stream, adapter and model classes must exist, andrelatedmust sit insideconfig. A related stream that is not registered yet is a warning. So is an@import that does not resolve. - A real build with
Streams::build().
php artisan streams:validate
php artisan streams:validate streams/posts.json streams/authors.json --json
The table output ends with N checked, M invalid.. --json prints an object keyed by file, each with valid, errors, and warnings. The exit code is non-zero when any file is invalid, so the command works as a CI or pre-commit check.
streams:schema
php artisan streams:schema {--include=} {--exclude=} {--path=}
Writes a JSON schema ({id}.schema.json) for the entries of every registered stream, built from Core's StreamSchema: the stream's tag metadata merged with its object schema. This is not the definition schema that $schema points at. --include and --exclude take comma-separated stream IDs. --path is relative to the project root and must already exist; it defaults to the project root.
mkdir -p storage/schemas
php artisan streams:schema --include=posts,authors --path=storage/schemas
streams:livewire
php artisan streams:livewire {stream} {--type=all} {--force}
Generates Livewire 3 components for a stream. --type is index, form, show, or all (the default). The components use the stream's repository and criteria, so they work with any source adapter.
| Type | Class | What it does |
|---|---|---|
index |
{Stream}Index |
Paginated, sortable table with a delete action |
form |
{Stream}Form |
Create and edit form, validated with rules taken from the stream's fields |
show |
{Stream}Show |
Read-only view of one entry |
Classes go in config('livewire.class_namespace') (default App\Livewire, so app/Livewire/BlogPostsIndex.php). Views go in resources/views/livewire/, named after the class in kebab case (blog-posts-index.blade.php). Protected fields are never rendered, and a generated integer or uuid key is left out of the form.
The command stops without writing anything if any target file exists, unless you pass --force. It does not register routes. It prints the routes to add to routes/web.php:
Route::get('/blog-posts', \App\Livewire\BlogPostsIndex::class)->name('blog_posts.index');
Route::get('/blog-posts/create', \App\Livewire\BlogPostsForm::class)->name('blog_posts.create');
Route::get('/blog-posts/{entry}/edit', \App\Livewire\BlogPostsForm::class)->name('blog_posts.edit');
Route::get('/blog-posts/{entry}', \App\Livewire\BlogPostsShow::class)->name('blog_posts.show');
The components are full-page, so they render inside your Livewire layout (config('livewire.layout')). Use Streams UI instead when you want a configured control panel rather than files you own and edit.
streams:admin has been removed. Generate the components with streams:livewire and put them behind your own layout and routes.
Not registered
These command classes exist in the SDK source but are not registered, so php artisan won't find them:
| Command | Intended purpose | State |
|---|---|---|
streams:show |
Show one stream's attributes | Implemented, registration commented out. Use the MCP describe-stream tool. |
entries:list |
Paginated table of a stream's entries | Implemented, registration commented out. Use the MCP list-entries tool. |
entries:show |
Show one entry | Implemented, registration commented out. Use the MCP read-entry tool. |
streams:describe |
Write streams/{id}.json by inspecting a URL, JSON, database table, or Eloquent model |
Implemented, registration commented out |
streams:tap |
Call a "tap" URL with query-string input | Not implemented (empty handler) |
How to extend it
make:entry prompts for each missing field through a console input bound as streams.console.inputs.{type}. SdkServiceProvider::registerInputs() binds string, boolean, select/enum, array, and object inputs. number, decimal, date, time, and datetime use the string input. integer is listed twice in that map; the later entry wins, so integer is also prompted as a string and IntegerConsoleInput is never bound. To prompt for a custom field type, set the full map in config('streams.console.inputs'). The config value replaces the default map, so copy the defaults from registerInputs() and add yours:
// config/streams/console.php
return [
'inputs' => [
// ...the SDK defaults...
'money' => \App\Console\Inputs\MoneyConsoleInput::class,
],
];