PHPackages                             pascualmg/symfony-command-ui - 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. [CLI &amp; Console](/categories/cli)
4. /
5. pascualmg/symfony-command-ui

ActiveSymfony-bundle[CLI &amp; Console](/categories/cli)

pascualmg/symfony-command-ui
============================

Web UI + API to execute Symfony console commands from the browser or any AI agent. Auto-discovers commands, dynamic forms, NDJSON streaming terminal.

v1.3.1(1mo ago)4193↓100%MITPHPPHP &gt;=7.1CI passing

Since Apr 14Pushed 1mo agoCompare

[ Source](https://github.com/pascualmg/symfony-command-ui)[ Packagist](https://packagist.org/packages/pascualmg/symfony-command-ui)[ RSS](/packages/pascualmg-symfony-command-ui/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (4)Versions (13)Used By (0)

symfony-command-ui
==================

[](#symfony-command-ui)

[![CI](https://github.com/pascualmg/symfony-command-ui/actions/workflows/ci.yml/badge.svg)](https://github.com/pascualmg/symfony-command-ui/actions/workflows/ci.yml)[![Latest Version](https://camo.githubusercontent.com/e38f5b2dc1fbe6031ab2c2d2558d51d9eae07d71635711a2763981541f22c59e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7061736375616c6d672f73796d666f6e792d636f6d6d616e642d75692e737667)](https://packagist.org/packages/pascualmg/symfony-command-ui)[![License](https://camo.githubusercontent.com/c0a3af444b05575cbb7d958ece83b2888d691f61068409b6b5eb06e92043ee21/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f7061736375616c6d672f73796d666f6e792d636f6d6d616e642d75692e737667)](LICENSE)

**Web UI + API to execute Symfony console commands from the browser — or from any AI agent.**

[![symfony-command-ui dashboard](https://raw.githubusercontent.com/pascualmg/symfony-command-ui/main/docs/screenshots/dashboard.png)](https://raw.githubusercontent.com/pascualmg/symfony-command-ui/main/docs/screenshots/dashboard.png)

Drop this bundle into any Symfony project (3.4 through 8.x, PHP 7.1+) and get:

- A **web dashboard** with independent cards for each command (form + terminal)
- **Real-time streaming** output via NDJSON protocol
- **Auto-discovery** of commands from your `InputDefinition` — zero manual config
- An **AI-ready API** that any LLM, agent, or MCP server can use to operate your app

> Production-tested across multiple Symfony projects. The official Symfony Flex recipe was merged into [`symfony/recipes-contrib`](https://github.com/symfony/recipes-contrib/pull/1972) on May 5, 2026 — a single `composer require` is now all you need.

```
composer require pascualmg/symfony-command-ui

```

---

Architecture
------------

[](#architecture)

```
                                    ┌─────────────────────────────────┐
                                    │        YOUR SYMFONY APP         │
                                    │                                 │
                                    │   src/Command/                  │
                                    │     app:users:sync              │
                                    │     app:payments:process        │
                                    │     app:reports:generate        │
                                    │     ...                         │
                                    └──────────┬──────────────────────┘
                                               │
                               bin/console list --format=json
                                               │
                  ┌────────────────────────────▼────────────────────────────┐
                  │              symfony-command-ui bundle                   │
                  │                                                         │
                  │  GET /commands ─── auto-discovery ─── whitelist filter  │
                  │       │                                                 │
                  │       ▼                                                 │
                  │  JSON config ──►  Web Component        │
                  │                    ┌──────────────────────────┐         │
                  │                    │ ┌──────────────────────┐ │         │
                  │                    │ │ app:users:sync       │ │         │
                  │                    │ │ [--limit ▼] [--dry ☐]│ │         │
                  │                    │ │ [Run] [Copy] [Clear] │ │         │
                  │                    │ │ ░░░ terminal ░░░░░░░ │ │         │
                  │                    │ └──────────────────────┘ │         │
                  │                    │ ┌──────────────────────┐ │         │
                  │                    │ │ app:payments:process │ │         │
                  │                    │ │ [--gateway ▼] ...    │ │         │
                  │                    │ │ [Run] [Copy] [Clear] │ │         │
                  │                    │ │ ░░░ terminal ░░░░░░░ │ │         │
                  │                    │ └──────────────────────┘ │         │
                  │                    └──────────────────────────┘         │
                  │                                                         │
                  │  POST /execute ─── Process ─── NDJSON stream ──► browser│
                  └─────────────────────────────────────────────────────────┘
                           │                              │
                      AI agents                       Humans
                    (HTTP + JSON)                   (browser UI)

```

Why this matters
----------------

[](#why-this-matters)

### For humans

[](#for-humans)

You maintain a Symfony app with 20+ console commands. Some you run daily, some monthly, some only when debugging. Today you SSH into the server, remember the exact syntax, type it out.

With this bundle: open a URL, click Run. Each command is an independent card with its own form and terminal. Outputs persist — run Stats while Generate JWT keeps its result.

### For AI agents

[](#for-ai-agents)

Your Symfony commands encapsulate business logic: process payments, sync users, generate reports, manage subscriptions. This bundle turns them into an HTTP API that any agent can use:

```
┌──────────────┐         ┌───────────────────┐         ┌──────────────┐
│   AI Agent   │── GET ─►│  /commands        │── JSON ─►│  "I can run  │
│  (Claude,    │         │  Auto-discovery   │         │  these 5     │
│   GPT, etc.) │         └───────────────────┘         │  commands"   │
│              │                                        └──────────────┘
│              │         ┌───────────────────┐         ┌──────────────┐
│              │── POST ►│  /execute         │─ NDJSON ►│  Streaming   │
│              │         │  {command, opts}  │         │  output      │
└──────────────┘         └───────────────────┘         └──────────────┘

```

This is essentially an **MCP-compatible endpoint** for your Symfony application. Any AI agent that can make HTTP calls can now operate your app's business logic through your existing console commands.

---

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

[](#quick-start)

### 1. Install

[](#1-install)

```
composer require pascualmg/symfony-command-ui
```

### 2. Register the bundle

[](#2-register-the-bundle)

```
// config/bundles.php
return [
    // ...
    Pascualmg\SymfonyCommandUI\SymfonyCommandUIBundle::class => ['all' => true],
];
```

### 3. Import routes

[](#3-import-routes)

```
# config/routes/symfony_command_ui.yaml
symfony_command_ui:
    resource: '@SymfonyCommandUIBundle/Resources/config/routes.php'
    prefix: /symfony-console
```

### 4. Configure your commands

[](#4-configure-your-commands)

```
# config/packages/symfony_command_ui.yaml
symfony_command_ui:
    route_prefix: /symfony-console
    allowed_commands:
        - app:users:list
        - app:payments:process
        - app:reports:generate
    overrides:
        app:payments:process:
            --gateway: [stripe, paypal, braintree]
            --limit: [10, 50, 100, 500]
```

### 5. Open your browser

[](#5-open-your-browser)

```
https://your-app.com/symfony-console

```

That's it. Your commands are auto-discovered and ready to use.

---

How it works
------------

[](#how-it-works)

### Request flow

[](#request-flow)

```
Browser opens /symfony-console
        │
        ▼
GET /commands ─────────────────────────────────────────────┐
        │                                                   │
        │   Backend runs: php bin/console list --format=json│
        │   Filters by allowed_commands whitelist            │
        │   Merges config overrides (dropdowns)              │
        │   Returns JSON array of commands                   │
        │                                                   │
        ▼                                                   │
 Web Component                              │
        │                                                   │
        │   Renders one card per command                     │
        │   Each card: name + description + form + terminal  │
        │                                                   │
        │   User clicks [Run] on a card                     │
        ▼                                                   │
POST /execute ──────────────────────────────────────────────┤
        │                                                   │
        │   Backend validates command ∈ whitelist             │
        │   Runs: php bin/console {command} {options}        │
        │   Streams stdout as NDJSON (line by line)          │
        │                                                   │
        ▼                                                   │
Terminal shows output in real-time                           │
        │                                                   │
        │   {"type":"line","text":"Processing..."}           │
        │   {"type":"line","text":"Done: 95 items"}          │
        │   {"type":"complete","exitCode":0,"duration":"2s"} │
        │                                                   │
        ▼                                                   │
[Copy] button copies clean output (no timestamps, no chrome)│
────────────────────────────────────────────────────────────┘

```

### Auto-discovery

[](#auto-discovery)

The bundle runs `php bin/console list --format=json` via `Symfony\Component\Process` and filters by your whitelist. Each command's `InputDefinition` is translated automatically:

Symfony InputDefinitionJSON valueUI element`InputOption::VALUE_NONE` (flag)`false`Checkbox (unchecked)`InputOption::VALUE_REQUIRED` with default`"default value"`Text input (pre-filled)`InputOption::VALUE_REQUIRED` without default`""`Text input (empty)`InputArgument` required`""`Text input (empty)Override with array values`["a", "b", "c"]`Dropdown select**Adding a new command = adding one line to `allowed_commands`.** The UI generates itself.

Want a dropdown for a specific option? Add it to `overrides`. Everything else is auto-discovered.

### NDJSON streaming protocol

[](#ndjson-streaming-protocol)

When you execute a command, the backend streams each line of stdout as a JSON object (Newline-Delimited JSON):

```
Content-Type: application/x-ndjson
X-Accel-Buffering: no

{"type":"line","text":"Processing batch 1..."}
{"type":"line","text":"[OK] 95 items processed"}
{"type":"batch","batch":1,"processed":95,"errors":2}
{"type":"complete","exitCode":0,"duration":"2.3s"}

```

The terminal renders each type with a different color:

TypeColorMeaning`line`GrayStandard output`batch`BlueBatch progress`complete` (exit 0)GreenSuccess`complete` (exit != 0)RedFailureNo WebSocket. No Server-Sent Events. Just `fetch()` + `ReadableStream` + `TextDecoder`. Works everywhere.

### Buffered response mode (`Accept: application/json`)

[](#buffered-response-mode-accept-applicationjson)

NDJSON streaming is great when you want to see progress in real time. But for short commands that emit structured output (a single JSON document, a CSV, a value), having to reassemble line-by-line on the client is friction. Since `v1.3.0` you can opt into a **buffered** response with content negotiation:

```
# Default: streaming NDJSON
curl -X POST "$BASE/execute" \
    -H "Content-Type: application/json" \
    -d '{"command":"app:stats","config":{"--json":true}}'

# Buffered: one shot, structured response
curl -X POST "$BASE/execute" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{"command":"app:stats","config":{"--json":true}}'
```

Buffered response shape:

```
{
  "command": "app:stats",
  "exitCode": 0,
  "duration": "4.6s",
  "stdout": "{\n    \"users\": 4532,\n    ...\n}\n",
  "stderr": "",
  "truncated": false
}
```

Now the client can `jq -r .stdout | jq` and get the original JSON without rebuilding it from NDJSON fragments.

**When to use which:**

ScenarioModeWhyLong-running commands (process, sync, consume)streaming (default)Live progressShort commands with JSON/structured output (`app:stats --json`, `app:user:show`)bufferedOne parse, no frictionChat ops where progress mattersstreamingPipe lines to channelAI agent calling RPC-style toolsbufferedSingle parse, atomic resultCache-clear, healthchecks, anything sub-secondeither, buffered is shorter to consumeOutput is capped per stream by `max_buffered_output_kb` (default 5 MB). If a command emits more, the stream is truncated and the response carries `"truncated": true` — the command is still allowed to finish so `exitCode` is meaningful. Lower the cap on memory-constrained servers, raise it for export-style commands:

```
# config/packages/symfony_command_ui.yaml
symfony_command_ui:
    max_buffered_output_kb: 51200  # 50 MB for big exports
```

Backwards compatible: clients that don't send `Accept` (or send `Accept: */*`, `application/x-ndjson`) keep getting NDJSON streaming, exactly as before.

### Card layout

[](#card-layout)

Each command renders as an **independent card**:

```
┌─────────────────────────────────────────────────────────┐
│  app:payments:process                                    │
│  Process pending payments                                │
│                                                          │
│  gateway: [stripe ▼]  limit: [100 ▼]  ☐ dry-run        │
│  [Run]  [Copy]  [Clear]                                  │
│  ┌────────────────────────────────────────────────────┐  │
│  │ $ bin/console app:payments:process --gateway=stripe│  │
│  │ Processing batch 1... 50 payments                  │  │
│  │ Processing batch 2... 48 payments                  │  │
│  │ [OK] exit=0 duration=3.2s                          │  │
│  └────────────────────────────────────────────────────┘  │
├─────────────────────────────────────────────────────────┤
│  app:reports:generate                                    │
│  Generate monthly reports                                │
│                                                          │
│  format: [pdf ▼]  ☐ json                                │
│  [Run]  [Copy]  [Clear]                                  │
│  ┌────────────────────────────────────────────────────┐  │
│  │ Ready                                              │  │
│  └────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘

```

- Each card has its **own terminal** — outputs persist across commands
- **Copy** copies clean output (no timestamps, no `[OK] exit=...` chrome)
- **Clear** resets only that card's terminal
- Cards can run **simultaneously** (each is independent)

---

Web Component
-------------

[](#web-component)

A single `` custom element with Shadow DOM. Zero dependencies. Served by the bundle as a static asset — no npm, no webpack, no build step.

```

```

### Theming

[](#theming)

Override CSS custom properties to match your app:

```
/* Dark theme (default) */
symfony-command {
    --cmd-bg: #0a0a1a;
    --cmd-surface: #1a1a2e;
    --cmd-text: #e0e0e0;
    --cmd-accent: #4ecca3;
}

/* Light theme */
symfony-command {
    --cmd-bg: #ffffff;
    --cmd-surface: #f8f9fa;
    --cmd-text: #212529;
    --cmd-accent: #0d6efd;
    --cmd-success: #198754;
    --cmd-error: #dc3545;
    --cmd-batch: #0d6efd;
    --cmd-info: #6c757d;
    --cmd-border: rgba(0,0,0,0.12);
    --cmd-font: 'SF Mono', 'Fira Code', monospace;
    --cmd-radius: 6px;
}
```

### Custom events

[](#custom-events)

```
const el = document.querySelector('symfony-command');
el.addEventListener('command-started', e => console.log('Started:', e.detail));
el.addEventListener('command-completed', e => console.log('Done:', e.detail));
el.addEventListener('command-error', e => console.log('Error:', e.detail));
```

---

Using with AI agents
--------------------

[](#using-with-ai-agents)

### Discovery + Execute (any HTTP client)

[](#discovery--execute-any-http-client)

```
import requests, json

BASE = "https://app.com/symfony-console"

# 1. Discover available commands
commands = requests.get(f"{BASE}/commands").json()
for cmd in commands:
    print(f"  {cmd['command']}: {cmd['label']}")
    # app:users:sync: Synchronize users with external services
    # app:payments:process: Process pending payments
    # ...

# 2. Execute a command
response = requests.post(f"{BASE}/execute",
    json={"command": "app:users:sync", "options": {"--limit": 100, "--dry-run": True}},
    stream=True
)

# 3. Stream output
for line in response.iter_lines():
    event = json.loads(line)
    print(event.get("text", ""))
    if event["type"] == "complete":
        print(f"Exit code: {event['exitCode']}, Duration: {event['duration']}")
```

### With Claude Code / MCP

[](#with-claude-code--mcp)

Wrap these two endpoints as MCP tools and your AI assistant can operate your Symfony app conversationally:

> **You**: "Check if there are any pending payments over $1000"
>
> **Claude**: *GET /commands → finds `app:payments:list`**POST /execute with `{"command":"app:payments:list","options":{"--min-amount":1000,"--status":"pending","--json":true}}`*
>
> **Claude**: "There are 3 pending payments over $1000: #4521 ($2,340), #4523 ($1,100), #4529 ($5,600). Want me to process them?"
>
> **You**: "Process them with dry-run first"
>
> **Claude**: *POST /execute with `{"command":"app:payments:process","options":{"--ids":"4521,4523,4529","--dry-run":true}}`*
>
> **Claude**: "Dry run complete. All 3 would process successfully. Total: $9,040. Run for real?"

### MCP tool definition example

[](#mcp-tool-definition-example)

```
{
  "name": "symfony_console",
  "description": "Execute Symfony console commands on the application server",
  "input_schema": {
    "type": "object",
    "properties": {
      "action": {"type": "string", "enum": ["discover", "execute"]},
      "command": {"type": "string"},
      "options": {"type": "object"}
    }
  }
}
```

---

Security
--------

[](#security)

**This bundle does NOT include authentication.** You must protect the routes yourself:

```
# Option 1: Symfony security
security:
    access_control:
        - { path: ^/symfony-console, roles: ROLE_ADMIN }

# Option 2: IP whitelist in your web server (nginx/apache)
# Option 3: VPN-only access
# Option 4: Your own middleware / event subscriber
```

The bundle provides `allowed_commands` as a whitelist — only commands in this list can be discovered and executed. But **route-level access control is your responsibility**.

---

Configuration reference
-----------------------

[](#configuration-reference)

```
symfony_command_ui:
    # URL prefix for all bundle endpoints
    route_prefix: /symfony-console        # default

    # Whitelist: only these commands can be discovered and executed
    allowed_commands:
        - app:my:command
        - app:another:command

    # Override auto-discovered options with rich UI elements
    # Arrays become dropdown selects instead of text inputs
    overrides:
        app:my:command:
            --option-name: [value1, value2, value3]
```

### Endpoints

[](#endpoints)

MethodPathDescriptionGET`{prefix}/`HTML page with `` Web ComponentGET`{prefix}/asset/symfony-command.js`The Web Component JS fileGET`{prefix}/commands`Auto-discovered command list (JSON)POST`{prefix}/execute`Execute a command. Returns NDJSON stream by default, or buffered JSON with `Accept: application/json` (since v1.3.0)---

Requirements
------------

[](#requirements)

- **PHP**: 7.1 through 8.3 (tested against every minor)
- **Symfony**: 3.4, 4.4, 5.4, 6.x, 7.x, 8.x

### Supported matrix

[](#supported-matrix)

Every combination below is exercised by CI (install in a real Symfony skeleton):

PHPSymfonyStatus7.13.4✓7.24.4✓7.45.4✓8.16.4✓8.27.0✓8.37.4✓Plus `php -l` on PHP 7.1, 7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.3.

The bundle has **zero runtime dependencies beyond `symfony/framework-bundle`, `symfony/process`, `symfony/http-foundation`, and `symfony/routing`** — all stable APIs since Symfony 3.x.

Contributing
------------

[](#contributing)

This bundle exists because I think the Symfony community needs it. If you agree, the best way to help is to use it, break it, and tell me what's missing.

- **Bugs and questions** → [open an issue](https://github.com/pascualmg/symfony-command-ui/issues). No issue is too small. "I expected X, got Y" is enough.
- **Pull requests** → very welcome. New filtering modes, new themes, accessibility improvements, translations of the UI labels, new examples in the docs, integrations with chat ops platforms (Slack/Telegram), MCP server adapters, you name it. Please run the existing CI matrix locally if your change touches the bundle code.
- **Real-world feedback** → if you ship this in production, I'd love to hear about it. Open a discussion or drop me a line. Real use cases drive the roadmap.

The goal is a small, sharp tool that does one thing well: turn any Symfony app into something humans and AI agents can both operate. Every contribution that pushes towards that goal is welcome.

License
-------

[](#license)

MIT — Pascual Munoz Galian

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance94

Actively maintained with recent releases

Popularity19

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity36

Early-stage or recently created project

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

Every ~2 days

Total

12

Last Release

33d ago

PHP version history (2 changes)v1.0.0PHP &gt;=7.4

v1.2.0PHP &gt;=7.1

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/11436672?v=4)[Pascual Muñoz ](/maintainers/pascualmg)[@pascualmg](https://github.com/pascualmg)

---

Tags

consolesymfonymcpstreamingNDJSONweb componentsai-agentscommand runnerweb-ui

### Embed Badge

![Health badge](/badges/pascualmg-symfony-command-ui/health.svg)

```
[![Health](https://phpackages.com/badges/pascualmg-symfony-command-ui/health.svg)](https://phpackages.com/packages/pascualmg-symfony-command-ui)
```

###  Alternatives

[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.4M195](/packages/sulu-sulu)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

585.4M506](/packages/shopware-core)[pimcore/pimcore

Content &amp; Product Management Framework (CMS/PIM/E-Commerce)

3.8k3.8M444](/packages/pimcore-pimcore)[drupal/core

Drupal is an open source content management platform powering millions of websites and applications.

21764.8M1.6k](/packages/drupal-core)[prestashop/prestashop

PrestaShop is an Open Source e-commerce platform, committed to providing the best shopping cart experience for both merchants and customers.

9.1k16.8k](/packages/prestashop-prestashop)[open-dxp/opendxp

Content &amp; Product Management Framework (CMS/PIM)

9017.2k55](/packages/open-dxp-opendxp)

PHPackages © 2026

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