Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message
Agents
njoguamos/laravel-waha
claude-code
cursor
saloon
waha
whatsapp-api
dto
pest
facades
http-client

This is Laravel WAHA - a PHP Laravel package that provides an elegant API client for the WAHA (WhatsApp HTTP API). The package uses Saloon PHP as its HTTP client foundation.

Install
php artisan boost:add-skill njoguamos/laravel-waha

Save this content to: AGENTS.md

# AI Development Instructions

## Project Overview

This is **Laravel WAHA** - a PHP Laravel package that provides an elegant API client for the [WAHA (WhatsApp HTTP API)](https://waha.example.com). The package uses [Saloon PHP](https://docs.saloon.dev/) as its HTTP client foundation.

- **Package Name:** `njoguamos/laravel-waha`
- **PHP Version:** ^8.4
- **Laravel Version:** ^12.x
- **License:** MIT
- **Author:** Njogu Amos ([email protected])

## Architecture

### Directory Structure

```text
├── config/           # Package configuration
├── docs/             # Documentation (VitePress)
├── src/              # Source code
│   ├── Dto/          # Data Transfer Objects
│   ├── Endpoints/    # API endpoint classes (Status, etc.)
│   ├── Facades/      # Laravel facades
│   └── Requests/     # Saloon request classes
├── tests/            # Pest PHP tests
│   ├── Feature/      # Feature tests
│   ├── Unit/         # Unit tests
│   ├── Pest.php      # Pest configuration
│   └── TestCase.php  # Base test case (not used for test files)
```

### Core Patterns
 
 1. **Saloon HTTP:** `WahaConnector` (auth & headers) → `Request` classes (per endpoint) → DTOs
 2. **Endpoints:** Extend `Waha` base class, return `Saloon\Http\Response`, accessible via facades. Use `dtoOrFail()` to transform the response into a typed DTO.
 3. **DTOs:** Have `toArray()` serialization. `fromArray()` is allowed only when needed. `createDtoFromResponse()` in `Request` classes is used for `dtoOrFail()`.
 4. **Testing:** All tests MUST use **Pest PHP** syntax (`test()` or `it()`). PHPUnit class-based tests are prohibited.
 5. **Service Provider:** Binds `WahaConnector` to container, publishes config
 6. **Facades:** Static access via `Status::sendText()` instead of `app(Status::class)->sendText()`

## Coding Standards

### PHP Style (Pint Configuration)

Run formatting with: `composer format`

Key rules from `pint.json`:

- **Preset:** PSR-12
- **Strict types:** Required (`declare(strict_types=1);`)
- **Array syntax:** Short `[]`
- **Imports:** Sorted by length
- **Class elements:** Ordered (traits, constants, properties, constructor, methods)
- **Binary operators:** Aligned (`=>`)
- **Date/Time immutability:** Encourages immutable datetime objects (`date_time_immutable`)
- **Multibyte strings:** Uses `mb_` string functions (`mb_str_functions`)
- **Modern type casting:** Modernizes type casting syntax (`modernize_types_casting`)
- **Global imports:** Imports classes, constants, and functions globally (`global_namespace_import`)
- **Access modifiers:** Converts `protected` to `private` where possible (`protected_to_private`)

### Code Style Examples

```php
<?php

declare(strict_types=1);

namespace NjoguAmos\Waha\Requests\Status;

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Contracts\Body\HasBody;
use Saloon\Traits\Body\HasJsonBody;
use NjoguAmos\Waha\Dto\TextStatusData;

class SendTextStatusRequest extends Request implements HasBody
{
    use HasJsonBody;

    protected Method $method = Method::POST;

    public function __construct(
        protected string $session,
        protected TextStatusData $data
    ) {
    }

    public function resolveEndpoint(): string
    {
        return '/api/'.rawurlencode($this->session).'/status/text';
    }

    public function defaultBody(): array
    {
        return $this->data->toArray();
    }
}
```

### DTO Example

```php
<?php

declare(strict_types=1);

namespace NjoguAmos\Waha\Dto;

class TextStatusData
{
    public function __construct(
        public string $text,
        public ?string $backgroundColor = null,
        public int $font = 1,
        public ?array $contact = null,
    ) {
    }

    public function toArray(): array
    {
        $array = [
            'text'            => $this->text,
            'backgroundColor' => $this->backgroundColor,
            'font'            => $this->font,
        ];

        if ($this->contact !== null) {
            $array['contacts'] = $this->contact;
        }

        return $array;
    }
}
```

## Testing

Run tests: `composer test -- --parallel`

- **`tests/Feature/`** - Integration tests (endpoint methods, HTTP cycles)
- **`tests/Unit/`** - Unit tests (DTOs, support classes)

### Key Patterns

```php
// Mock HTTP requests
MockClient::global([
    SendTextStatusRequest::class => MockResponse::make(['text' => 'Hello'])
]);

// Test DTOs
$statusData = new TextStatusData(text: 'Hello', backgroundColor: '#38b42f', font: 1);
expect($statusData->text)->toBe('Hello');
```

- Configure in `tests/Pest.php` with `Config::preventStrayRequests()` - all HTTP requests must be mocked
- Use `test()` or `it()` functions with descriptive names

## Environment Variables

```bash
WAHA_API_KEY=your-api-key
WAHA_BASE_URL=https://waha.example.com
```

## Service Provider & Configuration

The `WahaServiceProvider` (in `src/WahaServiceProvider.php`):

```php
public function register(): void
{
    $this->app->bind(
        abstract: WahaConnector::class,
        concrete: function (): WahaConnector {
            return new WahaConnector(
                baseUrl: config()->string(key: 'waha.base_url'),
                apiKey: config()->string(key: 'waha.api_key'),
            );
        }
    );
}

public function boot(): void
{
    $this->mergeConfigFrom(path: __DIR__ . '/../config/waha.php', key: 'waha');

    $this->publishes(paths: [
        __DIR__ . '/../config/waha.php' => config_path('waha.php'),
    ], groups: 'config');
}
```

Publish config: `php artisan vendor:publish --tag=config --provider="NjoguAmos\Waha\WahaServiceProvider"`

## Using Facades

```php
use NjoguAmos\Waha\Facades\Status;
use NjoguAmos\Waha\Dto\TextStatusData;

$statusData = new TextStatusData(
    text: 'Have a look! https://waha.example.com/',
    backgroundColor: '#38b42f',
    font: 1
);

$result = Status::sendText(session: 'default', data: $statusData);
```

Each facade implements `getFacadeAccessor()` pointing to its endpoint class.

## Available Commands

| Command           | Description                    |
|-------------------|--------------------------------|
| `composer test -- --parallel` | Run Pest tests (use --parallel for speed) |
| `composer format` | Run Laravel Pint (format code) |
| `composer lint`   | Check code style with Pint     |

## Adding New Features

### Adding a New API Endpoint

1. **Create the Request class** in `src/Requests/` (implement `HasBody` if needed):

```php
<?php

declare(strict_types=1);

namespace NjoguAmos\Waha\Requests\Something;

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Contracts\Body\HasBody;
use Saloon\Traits\Body\HasJsonBody;
use NjoguAmos\Waha\Dto\SomethingData;

class GetSomething extends Request implements HasBody
{
    use HasJsonBody;

    protected Method $method = Method::POST;

    public function __construct(
        protected string $session,
        protected SomethingData $data
    ) {
    }

    public function resolveEndpoint(): string
    {
        return '/api/'.rawurlencode($this->session).'/something';
    }

    public function defaultBody(): array
    {
        return $this->data->toArray();
    }
}
```

2. **Create the DTO** in `src/Dto/`:

```php
<?php

declare(strict_types=1);

namespace NjoguAmos\Waha\Dto;

class SomethingData
{
    public function __construct(public int $id, public string $name) {}
}
```

3. **Add to Endpoint** in `src/Endpoints/`, return `Saloon\Http\Response`, and write tests in `tests/Feature/`. Use `dtoOrFail()` on the response to access DTO data:

```php
// In Endpoint class
public function getSomething(string $session, SomethingData $data): Response
{
    return $this->connector->send(new GetSomething($session, $data));
}

// In usage/tests
$response = Status::getSomething(session: 'default', data: $somethingData);
$dto = $response->dtoOrFail();
```

## Base Class (Waha)

All endpoint classes extend the `NjoguAmos\Waha\Waha` base class, which provides internal utility methods:

- **`humanDelay(int $min = 1, int $max = 10): void`**: Adds a random sleep between `$min` and `$max` seconds to mimic human behavior.
- **`sendPresenceStatus(string $session, string $chatId): void`**: Sends a sequence of Online → Typing → Paused presence statuses with random delays to mimic human-like messaging behavior.

## Documentation Standards (refine-doc)

When updating documentation in `docs/`:

1. **Title and Subtitle:** `#` for main title, `##` for sections like `Usage`, `Response`, `Engines`, and `References`.
2. **Code Blocks:** Use `::: code-group` for multiple implementation examples (e.g., `[Saloon Response]` vs `[DTO]`).
3. **Response Section:** Describe the response type (`Saloon\Http\Response`) and show how to handle it using `json()` and `dtoOrFail()`.
4. **References:** Always include a `## References` section linking to official WAHA docs and relevant DTO references using site-absolute paths (`/reference/dto/<slug>.md`).
5. **Engines Table:** Include a compatibility table for different WAHA engines (WEBJS, WPP, NOWEB, GOWS).

## Important Notes

1. **Always use `declare(strict_types=1);`** at the top of every PHP file
2. **Type hint everything** - properties, parameters, return types
3. **Use constructor property promotion** where appropriate
4. **Prefer named arguments** for DTO constructors
5. **Handle null values** with null coalescing operator `??` in DTOs
6. **Use `dtoOrFail()`** when expecting DTOs from responses
7. **Mock all HTTP requests** in tests - stray requests are prevented
8. **Follow PSR-12** and run `composer format` before committing

# Use Semantic Commit Messages

- `feat`: (new feature for the user, not a new feature for a build script)
- `fix`: (bug fix for the user, not a fix to a build script)
- `docs`: (changes to the documentation)
- `style`: (formatting, missing semicolons, etc.; no production code change)
- `refactor`: (refactoring production code, e.g. renaming a variable)
- `test`: (adding missing tests, refactoring tests; no production code change)
- `chore`: (updating grunt tasks etc.; no production code change)

## Documentation

Full documentation is in `docs/` and can be built with VitePress.

AI Development Instructions

Project Overview

This is Laravel WAHA - a PHP Laravel package that provides an elegant API client for the WAHA (WhatsApp HTTP API). The package uses Saloon PHP as its HTTP client foundation.

  • Package Name: njoguamos/laravel-waha
  • PHP Version: ^8.4
  • Laravel Version: ^12.x
  • License: MIT
  • Author: Njogu Amos ([email protected])

Architecture

Directory Structure

├── config/           # Package configuration
├── docs/             # Documentation (VitePress)
├── src/              # Source code
│   ├── Dto/          # Data Transfer Objects
│   ├── Endpoints/    # API endpoint classes (Status, etc.)
│   ├── Facades/      # Laravel facades
│   └── Requests/     # Saloon request classes
├── tests/            # Pest PHP tests
│   ├── Feature/      # Feature tests
│   ├── Unit/         # Unit tests
│   ├── Pest.php      # Pest configuration
│   └── TestCase.php  # Base test case (not used for test files)

Core Patterns

  1. Saloon HTTP: WahaConnector (auth & headers) → Request classes (per endpoint) → DTOs
  2. Endpoints: Extend Waha base class, return Saloon\Http\Response, accessible via facades. Use dtoOrFail() to transform the response into a typed DTO.
  3. DTOs: Have toArray() serialization. fromArray() is allowed only when needed. createDtoFromResponse() in Request classes is used for dtoOrFail().
  4. Testing: All tests MUST use Pest PHP syntax (test() or it()). PHPUnit class-based tests are prohibited.
  5. Service Provider: Binds WahaConnector to container, publishes config
  6. Facades: Static access via Status::sendText() instead of app(Status::class)->sendText()

Coding Standards

PHP Style (Pint Configuration)

Run formatting with: composer format

Key rules from pint.json:

  • Preset: PSR-12
  • Strict types: Required (declare(strict_types=1);)
  • Array syntax: Short []
  • Imports: Sorted by length
  • Class elements: Ordered (traits, constants, properties, constructor, methods)
  • Binary operators: Aligned (=>)
  • Date/Time immutability: Encourages immutable datetime objects (date_time_immutable)
  • Multibyte strings: Uses mb_ string functions (mb_str_functions)
  • Modern type casting: Modernizes type casting syntax (modernize_types_casting)
  • Global imports: Imports classes, constants, and functions globally (global_namespace_import)
  • Access modifiers: Converts protected to private where possible (protected_to_private)

Code Style Examples

<?php

declare(strict_types=1);

namespace NjoguAmos\Waha\Requests\Status;

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Contracts\Body\HasBody;
use Saloon\Traits\Body\HasJsonBody;
use NjoguAmos\Waha\Dto\TextStatusData;

class SendTextStatusRequest extends Request implements HasBody
{
    use HasJsonBody;

    protected Method $method = Method::POST;

    public function __construct(
        protected string $session,
        protected TextStatusData $data
    ) {
    }

    public function resolveEndpoint(): string
    {
        return '/api/'.rawurlencode($this->session).'/status/text';
    }

    public function defaultBody(): array
    {
        return $this->data->toArray();
    }
}

DTO Example

<?php

declare(strict_types=1);

namespace NjoguAmos\Waha\Dto;

class TextStatusData
{
    public function __construct(
        public string $text,
        public ?string $backgroundColor = null,
        public int $font = 1,
        public ?array $contact = null,
    ) {
    }

    public function toArray(): array
    {
        $array = [
            'text'            => $this->text,
            'backgroundColor' => $this->backgroundColor,
            'font'            => $this->font,
        ];

        if ($this->contact !== null) {
            $array['contacts'] = $this->contact;
        }

        return $array;
    }
}

Testing

Run tests: composer test -- --parallel

  • tests/Feature/ - Integration tests (endpoint methods, HTTP cycles)
  • tests/Unit/ - Unit tests (DTOs, support classes)

Key Patterns

// Mock HTTP requests
MockClient::global([
    SendTextStatusRequest::class => MockResponse::make(['text' => 'Hello'])
]);

// Test DTOs
$statusData = new TextStatusData(text: 'Hello', backgroundColor: '#38b42f', font: 1);
expect($statusData->text)->toBe('Hello');
  • Configure in tests/Pest.php with Config::preventStrayRequests() - all HTTP requests must be mocked
  • Use test() or it() functions with descriptive names

Environment Variables

WAHA_API_KEY=your-api-key
WAHA_BASE_URL=https://waha.example.com

Service Provider & Configuration

The WahaServiceProvider (in src/WahaServiceProvider.php):

public function register(): void
{
    $this->app->bind(
        abstract: WahaConnector::class,
        concrete: function (): WahaConnector {
            return new WahaConnector(
                baseUrl: config()->string(key: 'waha.base_url'),
                apiKey: config()->string(key: 'waha.api_key'),
            );
        }
    );
}

public function boot(): void
{
    $this->mergeConfigFrom(path: __DIR__ . '/../config/waha.php', key: 'waha');

    $this->publishes(paths: [
        __DIR__ . '/../config/waha.php' => config_path('waha.php'),
    ], groups: 'config');
}

Publish config: php artisan vendor:publish --tag=config --provider="NjoguAmos\Waha\WahaServiceProvider"

Using Facades

use NjoguAmos\Waha\Facades\Status;
use NjoguAmos\Waha\Dto\TextStatusData;

$statusData = new TextStatusData(
    text: 'Have a look! https://waha.example.com/',
    backgroundColor: '#38b42f',
    font: 1
);

$result = Status::sendText(session: 'default', data: $statusData);

Each facade implements getFacadeAccessor() pointing to its endpoint class.

Available Commands

Command Description
composer test -- --parallel Run Pest tests (use --parallel for speed)
composer format Run Laravel Pint (format code)
composer lint Check code style with Pint

Adding New Features

Adding a New API Endpoint

  1. Create the Request class in src/Requests/ (implement HasBody if needed):
<?php

declare(strict_types=1);

namespace NjoguAmos\Waha\Requests\Something;

use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Contracts\Body\HasBody;
use Saloon\Traits\Body\HasJsonBody;
use NjoguAmos\Waha\Dto\SomethingData;

class GetSomething extends Request implements HasBody
{
    use HasJsonBody;

    protected Method $method = Method::POST;

    public function __construct(
        protected string $session,
        protected SomethingData $data
    ) {
    }

    public function resolveEndpoint(): string
    {
        return '/api/'.rawurlencode($this->session).'/something';
    }

    public function defaultBody(): array
    {
        return $this->data->toArray();
    }
}
  1. Create the DTO in src/Dto/:
<?php

declare(strict_types=1);

namespace NjoguAmos\Waha\Dto;

class SomethingData
{
    public function __construct(public int $id, public string $name) {}
}
  1. Add to Endpoint in src/Endpoints/, return Saloon\Http\Response, and write tests in tests/Feature/. Use dtoOrFail() on the response to access DTO data:
// In Endpoint class
public function getSomething(string $session, SomethingData $data): Response
{
    return $this->connector->send(new GetSomething($session, $data));
}

// In usage/tests
$response = Status::getSomething(session: 'default', data: $somethingData);
$dto = $response->dtoOrFail();

Base Class (Waha)

All endpoint classes extend the NjoguAmos\Waha\Waha base class, which provides internal utility methods:

  • humanDelay(int $min = 1, int $max = 10): void: Adds a random sleep between $min and $max seconds to mimic human behavior.
  • sendPresenceStatus(string $session, string $chatId): void: Sends a sequence of Online → Typing → Paused presence statuses with random delays to mimic human-like messaging behavior.

Documentation Standards (refine-doc)

When updating documentation in docs/:

  1. Title and Subtitle: # for main title, ## for sections like Usage, Response, Engines, and References.
  2. Code Blocks: Use ::: code-group for multiple implementation examples (e.g., [Saloon Response] vs [DTO]).
  3. Response Section: Describe the response type (Saloon\Http\Response) and show how to handle it using json() and dtoOrFail().
  4. References: Always include a ## References section linking to official WAHA docs and relevant DTO references using site-absolute paths (/reference/dto/<slug>.md).
  5. Engines Table: Include a compatibility table for different WAHA engines (WEBJS, WPP, NOWEB, GOWS).

Important Notes

  1. Always use declare(strict_types=1); at the top of every PHP file
  2. Type hint everything - properties, parameters, return types
  3. Use constructor property promotion where appropriate
  4. Prefer named arguments for DTO constructors
  5. Handle null values with null coalescing operator ?? in DTOs
  6. Use dtoOrFail() when expecting DTOs from responses
  7. Mock all HTTP requests in tests - stray requests are prevented
  8. Follow PSR-12 and run composer format before committing

Use Semantic Commit Messages

  • feat: (new feature for the user, not a new feature for a build script)
  • fix: (bug fix for the user, not a fix to a build script)
  • docs: (changes to the documentation)
  • style: (formatting, missing semicolons, etc.; no production code change)
  • refactor: (refactoring production code, e.g. renaming a variable)
  • test: (adding missing tests, refactoring tests; no production code change)
  • chore: (updating grunt tasks etc.; no production code change)

Documentation

Full documentation is in docs/ and can be built with VitePress.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast