{"id":50749947,"url":"https://github.com/codemonster-ru/annabel-framework","last_synced_at":"2026-06-11T00:30:55.675Z","repository":{"id":363852226,"uuid":"1258443961","full_name":"codemonster-ru/annabel-framework","owner":"codemonster-ru","description":"Read-only split of codemonster-ru/annabel packages/framework","archived":false,"fork":false,"pushed_at":"2026-06-10T16:24:05.000Z","size":151,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-10T17:20:55.488Z","etag":null,"topics":["annabel","monorepo-split","read-only"],"latest_commit_sha":null,"homepage":"https://github.com/codemonster-ru/annabel","language":"PHP","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codemonster-ru.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-03T15:27:56.000Z","updated_at":"2026-06-10T16:24:09.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/codemonster-ru/annabel-framework","commit_stats":null,"previous_names":["codemonster-ru/annabel-framework"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/codemonster-ru/annabel-framework","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codemonster-ru%2Fannabel-framework","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codemonster-ru%2Fannabel-framework/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codemonster-ru%2Fannabel-framework/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codemonster-ru%2Fannabel-framework/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codemonster-ru","download_url":"https://codeload.github.com/codemonster-ru/annabel-framework/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codemonster-ru%2Fannabel-framework/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34177445,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-10T02:00:07.152Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["annabel","monorepo-split","read-only"],"created_at":"2026-06-11T00:30:55.592Z","updated_at":"2026-06-11T00:30:55.658Z","avatar_url":"https://github.com/codemonster-ru.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003e [!IMPORTANT]\n\u003e This repository is read-only.\n\u003e\n\u003e Development happens in the Annabel monorepo:\n\u003e https://github.com/codemonster-ru/annabel\n\u003e\n\u003e Issues and pull requests should be opened there.\n\n# Annabel\r\n\r\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/codemonster-ru/annabel.svg?style=flat-square)](https://packagist.org/packages/codemonster-ru/annabel)\r\n[![Total Downloads](https://img.shields.io/packagist/dt/codemonster-ru/annabel.svg?style=flat-square)](https://packagist.org/packages/codemonster-ru/annabel)\r\n[![License](https://img.shields.io/packagist/l/codemonster-ru/annabel.svg?style=flat-square)](https://packagist.org/packages/codemonster-ru/annabel)\r\n[![Tests](https://github.com/codemonster-ru/annabel/actions/workflows/tests.yml/badge.svg)](https://github.com/codemonster-ru/annabel/actions/workflows/tests.yml)\r\n\r\nElegant and lightweight PHP framework for modern web applications.\r\n\r\n## Installation\r\n\r\n```bash\r\ncomposer require codemonster-ru/annabel\r\n```\r\n\r\n## Quick Start\r\n\r\n```php\r\n// public/index.php\r\nrequire __DIR__ . '/../vendor/autoload.php';\r\n\r\n$app = require __DIR__ . '/../bootstrap/app.php';\r\n$app-\u003erun();\r\n\r\n// bootstrap/app.php\r\nuse Codemonster\\Annabel\\Application;\r\n\r\n$baseDir = __DIR__ . '/..';\r\n\r\n$app = new Application($baseDir);\r\n\r\nrequire \"$baseDir/routes/web.php\";\r\n\r\nreturn $app;\r\n\r\n// routes/web.php\r\nrouter()-\u003eget('/', fn() =\u003e view('home', ['title' =\u003e 'Welcome to Annabel']));\r\n```\r\n\r\n## CLI\r\n\r\nAnnabel ships with a lightweight CLI similar to Laravel's `artisan`. It already supports:\n\r\n-   `about` - show version, base path, and loaded providers\n-   `route:list` - list registered routes\n-   `config:get key` - read a config value\n-   `config:list` - list config values with secrets redacted\n-   `container:list` - show container bindings/instances\n-   `vendor:publish` - publish package config, migrations, views, or assets\n-   `serve` - run PHP built-in server (default 127.0.0.1:8000)\n-   `make:controller`, `make:model`, `make:middleware`, `make:request`, `make:policy` - generate application classes\n-   With `codemonster-ru/database` installed: `make:migration`, `migrate`, `migrate:rollback`, `migrate:status`, `make:seed`, `seed` (appear in `annabel list`; connection is checked when commands run)\n\r\n```bash\r\nphp vendor/bin/annabel\nphp vendor/bin/annabel help\nphp vendor/bin/annabel help list\nphp vendor/bin/annabel make:controller Admin/User\nphp vendor/bin/annabel make:model User\nphp vendor/bin/annabel make:policy Post\nphp vendor/bin/annabel queue:work --once\nphp vendor/bin/annabel schedule:run\n```\n\nCommands may be registered by service providers and are resolved through the\napplication container, including constructor dependency injection:\n\n```php\nclass PackageServiceProvider extends ServiceProvider\n{\n    public function register(): void\n    {\n        $this-\u003ecommands([\n            SyncPackageCommand::class,\n        ]);\n    }\n}\n```\n\nNew commands may implement `execute(InputInterface $input, OutputInterface\n$output): int`. `ArgvInput` provides positional arguments and parsed long\noptions; commands return `ExitCode` constants. The legacy `handle(array)` method\nremains supported for existing commands.\n\n## Testing\n\nApplication tests can use Annabel's lightweight HTTP helpers:\n\n```php\nuse Codemonster\\Annabel\\Application;\nuse Codemonster\\Annabel\\Testing\\InteractsWithApplication;\nuse PHPUnit\\Framework\\TestCase;\n\nclass FeatureTest extends TestCase\n{\n    use InteractsWithApplication;\n\n    protected function createApplication(): Application\n    {\n        return require __DIR__ . '/../bootstrap/app.php';\n    }\n\n    public function test_homepage(): void\n    {\n        $this-\u003eget('/')-\u003eassertOk()-\u003eassertSee('Welcome');\n    }\n}\n```\n\n## Database Integration\n\r\nAnnabel ships with first-class integration for  \r\n[`codemonster-ru/database`](https://github.com/codemonster-ru/database).\r\n\r\n### 1. Create `config/database.php`\r\n\r\n```php\r\nreturn [\r\n    'default' =\u003e 'mysql',\r\n\r\n    'connections' =\u003e [\r\n        'mysql' =\u003e [\r\n            'driver'   =\u003e 'mysql',\r\n            'host'     =\u003e '127.0.0.1',\r\n            'port'     =\u003e 3306,\r\n            'database' =\u003e env('DB_NAME'),\r\n            'username' =\u003e env('DB_USER'),\r\n            'password' =\u003e env('DB_PASS'),\r\n            'charset'  =\u003e 'utf8mb4',\r\n        ],\r\n\r\n        'sqlite' =\u003e [\r\n            'driver'   =\u003e 'sqlite',\r\n            'database' =\u003e base_path('database/database.sqlite'),\r\n        ],\r\n    ],\r\n];\r\n```\r\n\r\n### 2. Usage\r\n\r\n```php\r\n// Query builder\r\n$users = db()-\u003etable('users')-\u003ewhere('active', 1)-\u003eget();\r\n\r\n// Schema builder\r\nschema()-\u003ecreate('posts', function ($table) {\r\n    $table-\u003eid();\r\n    $table-\u003estring('title');\r\n});\r\n\r\n// Transactions\r\ntransaction(function () {\r\n    db()-\u003etable('logs')-\u003einsert(['type' =\u003e 'created']);\r\n});\r\n```\r\n\r\n## Helpers\n\r\n| Function                | Description                        |\r\n| ----------------------- | ---------------------------------- |\r\n| `app()`                 | Access the application container   |\r\n| `base_path()`           | Resolve base project paths         |\r\n| `config()`              | Get or set configuration values    |\r\n| `env()`                 | Read environment variables         |\r\n| `dump()` / `dd()`       | Debugging utilities                |\r\n| `request()`             | Get current HTTP request           |\r\n| `response()` / `json()` | Create HTTP response               |\n| `http_client()`         | Access the HTTP client             |\n| `router()`              | Access router or register route    |\n| `route()`               | Generate a named route URI         |\n| `view()`                | Render or return view instance     |\n| `session()`             | Access session store               |\n| `storage()`             | Access filesystem storage disks    |\n| `old()`                 | Read flashed old form input        |\n| `errors()`              | Read flashed validation errors     |\n| `auth()`                | Access the authentication guard    |\n| `user()`                | Read the authenticated user        |\n| `cache()`               | Access PSR-16 cache store          |\n| `mailer()`              | Access mailers                     |\n| `queue()`               | Access queue connections           |\n| `dispatch()`            | Dispatch a queue job               |\n| `schedule()`            | Access scheduled tasks             |\n| `validator()`           | Validate input data                |\n| `db()`                  | Get the active database connection |\n| `schema()`              | Get the schema builder             |\r\n| `transaction()`         | Execute a DB transaction           |\r\n\r\nAll helpers are autoloaded automatically.\n\n## Filesystem\n\nAnnabel registers `codemonster-ru/filesystem` by default. Publish the default\nconfig and use `storage()` to read or write files:\n\n```bash\nphp vendor/bin/annabel vendor:publish --tag=filesystem\n```\n\n```php\nstorage('public')-\u003eput('avatars/user-1.txt', 'avatar');\n\n$url = storage('public')-\u003eurl('avatars/user-1.txt');\n```\n\n## HTTP Client\n\nAnnabel registers `codemonster-ru/http-client` by default. Configure defaults in\n`config/http-client.php` and use `http_client()` for external API calls:\n\n```php\n$response = http_client()\n    -\u003ebaseUrl('https://api.example.com')\n    -\u003eacceptJson()\n    -\u003eget('/users/1');\n\n$user = $response-\u003ethrow()-\u003ejson();\n```\n\n## Middleware\n\nAnnabel supports PSR-15 middleware via `Psr\\Http\\Server\\MiddlewareInterface`.\nRoute middleware may be registered by class name, and global middleware may be\nadded to the kernel with `addMiddleware()`.\n\nMiddleware aliases and groups keep routes compact:\n\n```php\nrouter()-\u003eget('/dashboard', [DashboardController::class, 'index'])\n    -\u003emiddleware('auth');\n\nrouter()-\u003eget('/posts/{post}', [PostController::class, 'show'])\n    -\u003emiddleware('can:posts.view,post');\n\nrouter()-\u003epost('/posts', [PostController::class, 'store'])\n    -\u003emiddleware('web');\n```\n\nThe framework registers `auth` and `can` when auth is enabled. The security\nprovider registers `csrf`, `throttle`, and the default `web` / `api` groups.\nCustom aliases and groups can be registered on the HTTP kernel:\n\n```php\napp(\\Codemonster\\Annabel\\Http\\Kernel::class)\n    -\u003ealiasMiddleware('admin', App\\Http\\Middleware\\AdminOnly::class);\n```\n\nPublish the security config to tune CSRF, rate-limit storage, trusted proxies,\nand named throttle presets:\n\n```bash\nphp vendor/bin/annabel vendor:publish --tag=security\n```\n\n```php\nrouter()-\u003epost('/login', [LoginController::class, 'store'])\n    -\u003emiddleware('throttle:login');\n```\n\n## Authentication\n\nAnnabel registers `codemonster-ru/auth` by default. Publish the default config\nand configure a user provider in `config/auth.php`, or provide a small in-memory\nlist for local applications:\n\n```bash\nphp vendor/bin/annabel vendor:publish --tag=auth\n```\n\n```php\nreturn [\n    'provider' =\u003e 'database',\n    'database' =\u003e [\n        'table' =\u003e 'users',\n        'identifier_column' =\u003e 'id',\n        'password_column' =\u003e 'password',\n    ],\n    'users' =\u003e [\n        new App\\User(1, 'admin@example.com', password_hash('secret', PASSWORD_DEFAULT)),\n    ],\n    'redirect_to' =\u003e '/login',\n];\n```\n\n```php\nif (auth()-\u003eattempt(['email' =\u003e $email, 'password' =\u003e $password])) {\n    return response()-\u003eredirect('/dashboard');\n}\n\nrouter()-\u003eget('/dashboard', [DashboardController::class, 'index'])\n    -\u003emiddleware('auth');\n```\n\nProduction applications should bind a database-backed\n`Codemonster\\Auth\\Contracts\\UserProviderInterface` implementation through\n`auth.provider`.\n\n## Routing\n\nRoutes support dynamic parameters, constraints, names, and URI generation:\n\n```php\nrouter()-\u003eget('/users/{id}', [UserController::class, 'show'])\n    -\u003ewhere('id', '\\d+')\n    -\u003ename('users.show');\n\nroute('users.show', ['id' =\u003e 42]); // /users/42\n```\n\nRoute parameters are injected into closures and controllers by parameter name.\nThe current `Codemonster\\Http\\Request` may be type-hinted alongside route\nparameters.\n\n## API Resources\n\nAPI resources provide one transformation for individual models, collections,\nand existing `simplePaginate()` results:\n\n```php\nuse Codemonster\\ApiResource\\JsonResource;\n\nfinal class UserResource extends JsonResource\n{\n    public function toArray(): array\n    {\n        return [\n            'id' =\u003e $this-\u003eresource-\u003egetKey(),\n            'name' =\u003e $this-\u003eresource-\u003ename,\n        ];\n    }\n}\n\nreturn UserResource::paginated(\n    User::query()-\u003esimplePaginate(20, $page),\n    '/api/users',\n)-\u003eresponse();\n```\n\n## Logging\n\nAnnabel binds `Psr\\Log\\LoggerInterface` in the container. Configure the default\nchannel in `config/logging.php`; unhandled HTTP exceptions are reported before\nthe error response is rendered.\n\n## Cache\n\nAnnabel binds `Psr\\SimpleCache\\CacheInterface` in the container. Configure the\ndefault store in `config/cache.php`; the framework ships with `array`, `file`,\nand `redis` stores. Set `CACHE_STORE=redis` and configure `REDIS_HOST`,\n`REDIS_PORT`, `REDIS_PASSWORD`, and `REDIS_CACHE_DB` for shared cache in\nmulti-instance deployments.\n\n## Mail\n\nAnnabel registers `codemonster-ru/mail` by default. Configure the default\nmailer in `config/mail.php`; the framework ships with `array`, `log`,\n`sendmail`, and Symfony-powered `smtp` transports. Set `MAIL_MAILER=smtp` and\nprovide an SMTP DSN through `MAILER_DSN`.\n\n```php\nuse Codemonster\\Mail\\Message;\n\nmailer('log')-\u003esend(\n    Message::make()\n        -\u003efrom('hello@example.com', 'Annabel')\n        -\u003eto('user@example.com')\n        -\u003esubject('Welcome')\n        -\u003etext('Welcome to Annabel.'),\n);\n```\n\n## Queue\n\nAnnabel registers `codemonster-ru/queue` by default. Configure the default\nconnection in `config/queue.php`; the framework ships with `sync`, `database`,\nand `redis` drivers.\n\n```php\nuse Codemonster\\Queue\\Contracts\\JobInterface;\n\nclass SendWelcomeEmailJob implements JobInterface\n{\n    public function handle(): void\n    {\n        //\n    }\n}\n\ndispatch(new SendWelcomeEmailJob());\n```\n\nThe default `sync` connection runs jobs immediately. For SQL-backed background\njobs, set `QUEUE_CONNECTION=database`, publish queue migrations, run `migrate`,\nand start the worker:\n\n```bash\nphp vendor/bin/annabel vendor:publish --tag=queue-migrations\nphp vendor/bin/annabel migrate\nphp vendor/bin/annabel queue:work\nphp vendor/bin/annabel queue:work --stop-when-empty\nphp vendor/bin/annabel queue:failed\nphp vendor/bin/annabel queue:retry 1\nphp vendor/bin/annabel queue:retry all\nphp vendor/bin/annabel queue:flush\n```\n\nFor Redis-backed workers, set `QUEUE_CONNECTION=redis` and configure\n`REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWORD`, `REDIS_QUEUE_DB`, and\n`QUEUE_REDIS_PREFIX`. Redis failed jobs are stored in Redis and work with the\nsame `queue:failed`, `queue:retry`, and `queue:flush` commands.\n\n## Scheduler\n\nAnnabel registers `codemonster-ru/scheduler` by default. Define tasks in\n`routes/schedule.php` and run `schedule:run` every minute from cron:\n\n```php\nuse Codemonster\\Scheduler\\Schedule;\n\n/** @var Schedule $schedule */\n$schedule-\u003ecall(fn () =\u003e cleanup(), 'cleanup')\n    -\u003edailyAt('03:00')\n    -\u003ewithoutOverlapping();\n```\n\n```bash\n* * * * * php /path/to/app/vendor/bin/annabel schedule:run\n```\n\nUse `schedule:list` to inspect registered tasks, cron expressions, due status,\nand overlap locks.\n\nScheduler locks use the configured cache store when the cache provider is\nregistered.\n\n## Production optimization\n\nBuild configuration and route caches during deployment:\n\n```bash\nphp vendor/bin/annabel optimize\n```\n\nRoutes with closures cannot be cached. Use controller handlers such as\n`[HomeController::class, 'index']`. Clear all generated caches before changing\nenvironment configuration or when troubleshooting:\n\n```bash\nphp vendor/bin/annabel optimize:clear\n```\n\nThe individual `config:cache`, `config:clear`, `route:cache`, and `route:clear`\ncommands are also available.\n\n## Events\n\nAnnabel binds `Psr\\EventDispatcher\\EventDispatcherInterface` and\n`Psr\\EventDispatcher\\ListenerProviderInterface`. Register listeners through the\nframework listener provider and dispatch events through the PSR dispatcher.\n\n## Validation\n\nAnnabel ships with a small validation layer for request/config data. It supports\ncommon scalar rules, nested fields through dot notation, `validated()` data, and\n`validateOrFail()` for exception-driven flows.\n\n```php\n$result = validator([\n    'email' =\u003e 'hello@example.com',\n], [\n    'email' =\u003e 'required|email',\n]);\n\nif ($result-\u003efails()) {\n    $errors = $result-\u003eerrors();\n}\n```\n\nControllers can use `Codemonster\\Annabel\\Http\\ValidatesRequests` to validate the\ncurrent request. Validation failures return JSON `422` responses for API\nrequests, or redirect back with flashed `errors` and `_old_input` for web forms.\nRedirects are restricted to local same-origin locations. Sensitive fields are\nexcluded recursively according to `config/validation.php`.\n\n```php\nuse Codemonster\\Annabel\\Http\\ValidatesRequests;\nuse Codemonster\\Http\\Request;\n\nclass RegisterController\n{\n    use ValidatesRequests;\n\n    public function store(Request $request): mixed\n    {\n        $data = $this-\u003evalidate($request, [\n            'email' =\u003e 'required|email',\n        ]);\n\n        // ...\n    }\n}\n```\n\n## HTTP Exceptions\n\nFramework HTTP exceptions live under `Codemonster\\Annabel\\Http\\Exceptions`.\nThey expose stable status and header contracts for bad requests, authentication,\nauthorization, missing routes, and unsupported methods.\n\n## Container parameters\n\nThe Annabel container implements `Psr\\Container\\ContainerInterface`, so it can be\npassed to libraries expecting a PSR-11 container.\n\nYou can pass named constructor parameters when resolving classes or closure bindings:\n\r\n```php\r\n$user = app(User::class, ['name' =\u003e 'Annabel']);\r\n\r\napp()-\u003ebind(User::class, fn($container, array $params) =\u003e new User($params['name']));\r\n$user = app(User::class, ['name' =\u003e 'Annabel']);\r\n\r\n// Same for Application::make()\r\n$user = $app-\u003emake(User::class, ['name' =\u003e 'Annabel']);\r\n```\r\n\r\nNote: for singleton bindings, passing parameters after the instance is resolved throws an exception.\n\nNote: `Application::serve()` will throw if an instance already exists; call `Application::resetInstance()` first.\n\n## Providers\n\nAnnabel reads provider settings from `config/app.php` before registering services.\n\n```php\nreturn [\n    'providers' =\u003e [\n        'defaults' =\u003e true,\n        'disabled' =\u003e [],\n        'extra' =\u003e [],\n        'discover' =\u003e true,\n        'path' =\u003e base_path('bootstrap/providers'),\n        'packages' =\u003e [\n            'discover' =\u003e true,\n            'dont_discover' =\u003e [],\n            'cache' =\u003e true,\n            'cache_path' =\u003e base_path('bootstrap/cache/packages.php'),\n        ],\n    ],\n];\n```\n\nAll providers are registered first and booted after registration completes.\n\nInstalled packages may declare providers in Composer metadata:\n\n```json\n{\n    \"extra\": {\n        \"annabel\": {\n            \"providers\": [\n                \"Vendor\\\\Package\\\\PackageServiceProvider\"\n            ]\n        }\n    }\n}\n```\n\nOnly providers owned by that package should be declared. Applications can\ndisable selected packages through `providers.packages.dont_discover`, use `*`\nto disable all package discovery, or set `providers.packages.discover` to\n`false`. The generated manifest cache is invalidated when package\n`composer.json` metadata changes.\n\n### Publishable Resources\n\nPackage providers may register publishable files or directory trees:\n\n```php\nclass PackageServiceProvider extends ServiceProvider\n{\n    public function boot(): void\n    {\n        $this-\u003epublishes([\n            __DIR__ . '/../../config/package.php' =\u003e base_path('config/package.php'),\n            __DIR__ . '/../../resources/views' =\u003e base_path('resources/views/vendor/package'),\n        ], ['config', 'package']);\n    }\n}\n```\n\nPublishing is explicit and does not overwrite existing files unless requested:\n\n```bash\nphp vendor/bin/annabel vendor:publish --provider=\"Vendor\\\\Package\\\\PackageServiceProvider\"\nphp vendor/bin/annabel vendor:publish --tag=config\nphp vendor/bin/annabel vendor:publish --all --force\n```\n\nDestinations must remain inside the application base path. Symbolic-link escape\npaths are rejected.\n\n## Testing\n\r\n```bash\r\ncomposer test\r\n```\r\n\r\n## Author\r\n\r\n[**Kirill Kolesnikov**](https://github.com/KolesnikovKirill)\r\n\r\n## License\r\n\r\n[MIT](https://github.com/codemonster-ru/annabel/blob/main/LICENSE)\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodemonster-ru%2Fannabel-framework","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodemonster-ru%2Fannabel-framework","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodemonster-ru%2Fannabel-framework/lists"}