smithery/koriym

malt

Malt development environment manager. Use when setting up, starting, stopping, troubleshooting, customizing Homebrew-based dev environments with malt.json, or converting malt.json to other formats (Docker, Kubernetes, Terraform, Ansible, GitHub Actions, .env).

Installation

$ npx skills add smithery/koriym --skill malt

Also in this package

Other skills from smithery/koriym.

npx skills add smithery/koriym

Browse all from smithery/koriym

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 17,035 B
  • docs SUMMARY.md 398 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

Malt - JSON-driven Homebrew Dev Services

Malt creates project-specific development environments using only Homebrew. Define your stack in malt.json, and Malt handles installation, configuration, and service management with native performance and port-based isolation.

Command Reference

Command Description
malt init Create malt.json from default template (skips if exists)
malt install Install Homebrew packages and PHP extensions from malt.json
malt create Generate malt/ directory with service configs (skips if exists)
malt start Create temp configs with variable substitution and start services
malt stop Stop services and clean up temp files
malt status Show running status of all configured services
malt kill Force-kill all Malt service processes (SIGKILL)
malt env Output shell script for env vars and aliases. Usage: source <(malt env)
malt info Show project name, directory, and configured ports

Setup Guide

Prerequisites

Before any malt operation, verify Homebrew and Malt are installed:

which brew   # Homebrew required
which malt   # Malt CLI required

If Homebrew is missing, direct the user to https://brew.sh/. If Malt is missing:

brew tap shivammathur/php
brew tap shivammathur/extensions
brew tap koriym/malt
brew install malt

Setup Flow

When composer.json exists (PHP project)

  1. Read composer.json to auto-detect requirements:
Source Pattern Service
composer.json php require constraint PHP version (e.g., ^8.1 -> [email protected])
composer.json ext-redis or predis/predis Redis
composer.json ext-memcached Memcached
composer.json ext-pdo_mysql MySQL
Filesystem .htaccess file exists Apache (httpd); otherwise Nginx
  1. Report detected dependencies and generate malt.json:
Detected these external dependencies from composer.json. Adding to malt.json:
- PHP 8.4 (port: 9000)
- MySQL 8.0 (port: 3306)
- Redis (port: 6379)
- Nginx (port: 80)
- Extensions: xdebug, redis
  1. Generate malt.json and run setup commands.

When composer.json does not exist

Ask the user what services they need in natural language. Example interactions:

  • "MySQL and Redis are needed" -> generate malt.json with MySQL and Redis
  • "Set up a PHP 8.4 environment with Nginx" -> generate accordingly

Then present the proposed malt.json and confirm with AskUserQuestion before proceeding.

Setup Commands

cd your-project
malt init              # 1. Create malt.json
# Edit malt.json to match your project
malt install           # 2. Install Homebrew packages and PHP extensions
malt create            # 3. Generate malt/ directory with service configs
malt start             # 4. Start all configured services
source <(malt env)     # 5. Set up PATH, env vars, and aliases

Step 3 (malt create) is required, not optional. malt start reads configs from malt/ and fails with "Malt directory not found" if the directory does not exist. Before invoking malt start, verify malt/ is present and run malt create first if it is missing.

Joining Existing Project

For projects with malt.json already committed, the malt/ directory may or may not be present. malt.json is committed, but malt/ is often regenerated per-checkout. Always check before starting:

malt install
[ -d malt ] || malt create
malt start
source <(malt env)

Precondition: malt start requires the malt/ directory. If only malt.json is present, run malt create first; otherwise malt start will fail with "Malt directory not found".

malt.json Schema

All fields are required. Schema: docs/schema.json

{
  "project_name": "myapp",
  "dependencies": [
    "[email protected]",
    "[email protected]",
    "composer",
    "redis",
    "nginx",
    "memcached"
  ],
  "ports": {
    "php": [9000],
    "mysql": [3306],
    "nginx": [80, 443],
    "httpd": [8080],
    "redis": [6379],
    "memcached": [11211]
  },
  "php_extensions": [
    "xdebug",
    "pcov",
    "redis",
    "memcached",
    "apcu"
  ]
}
Field Type Description
project_name string Project identifier
dependencies string[] Homebrew formula names (services, tools, PHP version)
ports object Service name -> port array. Keys: php, mysql, nginx, httpd, redis, memcached. Multiple ports create multiple instances
php_extensions string[] PHP extensions to install via shivammathur/extensions tap

Troubleshooting Guide

Detecting State: malt.json Exists but malt/ Does Not

Common on a fresh checkout: the project commits malt.json but malt/ is gitignored or has never been generated. Running malt start in this state fails with "Malt directory not found".

Correct response:

malt create && malt start

Not malt start alone. The skill should test for malt/ (e.g. [ -d malt ]) before invoking malt start and run malt create when the directory is absent.

Port Already in Use

# Check what's using a port
lsof -i :PORT_NUMBER
# Kill process on port
kill -9 $(lsof -t -i :PORT_NUMBER)

Or change the port in malt.json, then rm -rf malt/ && malt create && malt start.

MySQL First Start Fails

MySQL auto-initializes its data directory on first start (--initialize-insecure). If it fails:

rm -rf malt/var/mysql_*
malt start  # Will reinitialize

PHP Extension Load Failure

Extensions are resolved from Homebrew opt directories. If an extension fails to load:

  1. Check it's listed in malt.json php_extensions
  2. Run malt install to ensure it's installed
  3. Check malt/conf/php.ini for correct paths
  4. Extension resolution order: {HOMEBREWPREFIX}/opt/{ext}@{phpversion}/{ext}.so, then php{ext}@..., then php-{ext}@...

Service Won't Stop

malt kill  # Force-kill all Malt processes

malt create Does Nothing

The malt/ directory already exists. To regenerate:

rm -rf malt/
malt create
# Re-apply any customizations

Debug Mode

MALT_DEBUG=1 malt start   # Keep temp files, show variable substitution details
MALT_DEBUG=1 malt stop

Check Logs

Service logs are in malt/logs/:

  • php-fpm_9000.log
  • mysql3306error.log
  • nginxerror80.log
  • httpderror8080.log

Customization

Edit files in malt/conf/ directly, then restart with malt stop && malt start.

Change Document Root

Edit root in malt/conf/nginx_*.conf:

root "{{PROJECT_DIR}}/webroot";  # instead of public

Or DocumentRoot in malt/conf/httpd_*.conf:

DocumentRoot "{{PROJECT_DIR}}/webroot"
<Directory "{{PROJECT_DIR}}/webroot">

PHP Settings

Edit malt/conf/php.ini:

memory_limit = 512M
upload_max_filesize = 100M
post_max_size = 100M
display_errors = On
error_reporting = E_ALL

Xdebug Configuration

Append to malt/conf/php.ini:

[xdebug]
xdebug.mode = debug,develop
xdebug.start_with_request = yes
xdebug.client_host = 127.0.0.1
xdebug.client_port = 9003

MySQL Character Set

Edit malt/conf/my_*.cnf:

[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci

Re-generating Configs

After changing dependencies or ports in malt.json:

malt install           # Install new dependencies
rm -rf malt/           # Remove old configs
malt create            # Regenerate
# Re-apply customizations
malt start

Template Variables

Config files in malt/conf/ use {{VARIABLE}} placeholders, expanded at malt start into .tmp files:

Variable Description Example
{{MALT_DIR}} Absolute path to malt/ directory /Users/dev/myapp/malt
{{PROJECT_DIR}} Absolute path to project root /Users/dev/myapp
{{HOMEBREW_PREFIX}} Homebrew installation prefix /opt/homebrew
{{PHP_VERSION}} PHP version from dependencies 8.4
{{PORT}} Service port number 9000
{{INDEX}} Zero-based index (multi-instance) 0
{{PHP_EXTENSIONS}} Generated extension directives extension=...
{{NGINX_INCLUDES}} Generated include statements include ...;
{{PHP_PORT}} First configured PHP port 9000
{{PHPLIBPATH}} Path to Apache PHP module /opt/homebrew/opt/[email protected]/...

Directory Structure

your-project/
├── malt.json              # Environment definition (commit this)
├── malt/                  # Generated by malt create
│   ├── conf/              # Service config files (commit these)
│   │   ├── php-fpm_9000.conf
│   │   ├── php.ini
│   │   ├── nginx_80.conf
│   │   ├── nginx_main.conf
│   │   ├── httpd_8080.conf
│   │   ├── my_3306.cnf
│   │   ├── redis_6379.conf
│   │   └── *.tmp           # Temporary files (gitignore)
│   ├── logs/               # Service log files (gitignore)
│   ├── tmp/                # Temporary/socket files (gitignore)
│   └── var/                # Data files like MySQL data (gitignore)
└── public/                 # Document root for web servers

Recommended .gitignore

malt/logs/
malt/tmp/
malt/var/
malt/conf/*.tmp

Format Conversion

Use malt.json as Single Source of Truth to generate other formats.

Supported Formats

Dockerfile, Docker Compose, .env, GitHub Actions, Kubernetes, Terraform (AWS), Ansible.

Mapping Table: Docker / .env / GitHub Actions

malt.json Docker image .env GitHub Actions
[email protected] php:8.4-fpm PHP_VERSION=8.4 shivammathur/setup-php php-version: 8.4
[email protected] mysql:8.0 MYSQL_PORT=3306 services.mysql image: mysql:8.0
redis redis:latest REDIS_PORT=6379 services.redis
nginx nginx:latest NGINX_PORT=80 N/A
memcached memcached:latest MEMCACHED_PORT=11211 services.memcached

Mapping Table: Kubernetes / Terraform / Ansible

malt.json Kubernetes Terraform (AWS) Ansible
[email protected] Deployment + Service ECS task definition php-fpm package + systemd
[email protected] StatefulSet + PVC RDS mysql 8.0 mysql-server + systemd
redis Deployment + Service ElastiCache Redis redis-server + systemd
nginx Deployment + Ingress ALB nginx + systemd
memcached Deployment + Service ElastiCache Memcached memcached + systemd
ports Service ports Security Group ingress listen config
php_extensions custom image ECS image build pecl install tasks

Dockerfile

Generate a Dockerfile from malt.json. Typically one Dockerfile per service (used with Docker Compose):

FROM php:8.4-fpm
RUN docker-php-ext-install pdo_mysql \
    && pecl install redis apcu xdebug \
    && docker-php-ext-enable redis apcu xdebug
COPY . /var/www/html
WORKDIR /var/www/html
EXPOSE 9000

Key rules: use official base images, match PHP version and extensions from malt.json. Generate separate Dockerfiles per service when used with Docker Compose.

Docker Compose

Read malt.json and generate docker-compose.yml:

services:
  php:
    image: php:8.4-fpm
    ports:
      - "9000:9000"
    volumes:
      - .:/var/www/html
  mysql:
    image: mysql:8.0
    ports:
      - "3306:3306"
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
    volumes:
      - mysql_data:/var/lib/mysql
  redis:
    image: redis:latest
    ports:
      - "6379:6379"
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - .:/var/www/html
volumes:
  mysql_data:

.env

Generate environment variables from malt.json:

PHP_VERSION=8.4
PHP_PORT=9000
MYSQL_PORT=3306
MYSQL_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_HOST=127.0.0.1
NGINX_PORT=80

GitHub Actions

Generate CI workflow using shivammathur/setup-php for PHP and GitHub Actions services:

name: CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
        ports:
          - 3306:3306
      redis:
        image: redis
        ports:
          - 6379:6379
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'
          extensions: redis, pdo_mysql
      - run: composer install
      - run: composer test

Kubernetes

Generate Deployment + Service manifests per service in malt.json:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-fpm
spec:
  replicas: 1
  selector:
    matchLabels:
      app: php-fpm
  template:
    metadata:
      labels:
        app: php-fpm
    spec:
      containers:
        - name: php-fpm
          image: php:8.4-fpm
          ports:
            - containerPort: 9000
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: mysql:8.0
          env:
            - name: MYSQL_ALLOW_EMPTY_PASSWORD
              value: "yes"
          ports:
            - containerPort: 3306
          volumeMounts:
            - name: mysql-data
              mountPath: /var/lib/mysql
  volumeClaimTemplates:
    - metadata:
        name: mysql-data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 10Gi

Key rules: MySQL/stateful services use StatefulSet + PVC. Stateless services (PHP-FPM, Nginx, Redis, Memcached) use Deployment. Add resource requests/limits. Use ConfigMap for custom configs.

Terraform

Generate AWS infrastructure from malt.json (ECS Fargate + RDS):

# main.tf
resource "aws_ecs_task_definition" "app" {
  family                   = "myapp"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = "512"
  memory                   = "1024"

  container_definitions = jsonencode([
    {
      name  = "php-fpm"
      image = "php:8.4-fpm"
      portMappings = [{ containerPort = 9000 }]
    },
    {
      name  = "nginx"
      image = "nginx:latest"
      portMappings = [{ containerPort = 80 }]
    }
  ])
}

resource "aws_db_instance" "mysql" {
  engine         = "mysql"
  engine_version = "8.0"
  instance_class = "db.t3.micro"
  port           = 3306
}

resource "aws_elasticache_cluster" "redis" {
  engine         = "redis"
  node_type      = "cache.t3.micro"
  num_cache_nodes = 1
  port           = 6379
}

Key rules: MySQL → RDS, Redis/Memcached → ElastiCache, PHP+Nginx → ECS Fargate. Add VPC, subnets, security groups with port-based ingress matching malt.json ports. Organize as main.tf, variables.tf, outputs.tf.

Ansible

Generate playbook to provision a Linux server from malt.json:

---
- name: Setup development environment
  hosts: all
  become: true
  vars:
    php_version: "8.4"
    mysql_port: 3306
    redis_port: 6379
    nginx_port: 80

  tasks:
    - name: Install PHP and extensions
      apt:
        name:
          - "php{{ php_version }}-fpm"
          - "php{{ php_version }}-mysql"
          - "php{{ php_version }}-redis"
          - "php{{ php_version }}-apcu"
        state: present
      notify: restart php-fpm

    - name: Install MySQL
      apt:
        name: mysql-server-8.0
        state: present
      notify: restart mysql

    - name: Install Redis
      apt:
        name: redis-server
        state: present
      notify: restart redis

    - name: Install Nginx
      apt:
        name: nginx
        state: present
      notify: restart nginx

  handlers:
    - name: restart php-fpm
      systemd:
        name: "php{{ php_version }}-fpm"
        state: restarted

    - name: restart mysql
      systemd:
        name: mysql
        state: restarted

    - name: restart redis
      systemd:
        name: redis-server
        state: restarted

    - name: restart nginx
      systemd:
        name: nginx
        state: restarted

Key rules: Map each dependency to apt packages. Use handlers for service restarts. Extract ports and versions into vars. Add configuration templates for custom settings from malt/conf/.