From 8b8a9c26e5d31201dddc08606507b212963774c3 Mon Sep 17 00:00:00 2001 From: Snider Date: Wed, 4 Mar 2026 12:15:13 +0000 Subject: [PATCH] feat: extract Brain operations into CorePHP Actions + API routes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create 4 Actions in Actions/Brain/ (RememberKnowledge, RecallKnowledge, ForgetKnowledge, ListKnowledge) using the Action trait pattern - Slim MCP tool handlers to thin wrappers calling Actions - Add BrainController with REST endpoints (remember, recall, forget, list) - Add API route file with api.auth + api.scope.enforce middleware - Wire ApiRoutesRegistering in Boot.php - Rename routes/ → Routes/ to match CorePHP convention - Remove empty database/migrations/ (legacy Laravel boilerplate) Co-Authored-By: Virgil --- Actions/Brain/ForgetKnowledge.php | 62 +++++++++ Actions/Brain/ListKnowledge.php | 57 ++++++++ Actions/Brain/RecallKnowledge.php | 85 ++++++++++++ Actions/Brain/RememberKnowledge.php | 94 ++++++++++++++ Boot.php | 10 ++ Controllers/Api/BrainController.php | 164 ++++++++++++++++++++++++ Mcp/Tools/Agent/Brain/BrainForget.php | 35 +---- Mcp/Tools/Agent/Brain/BrainList.php | 25 +--- Mcp/Tools/Agent/Brain/BrainRecall.php | 41 +----- Mcp/Tools/Agent/Brain/BrainRemember.php | 53 +------- {routes => Routes}/admin.php | 0 Routes/api.php | 27 ++++ {routes => Routes}/console.php | 0 {routes => Routes}/web.php | 0 database/migrations/.gitkeep | 0 routes/api.php | 3 - 16 files changed, 515 insertions(+), 141 deletions(-) create mode 100644 Actions/Brain/ForgetKnowledge.php create mode 100644 Actions/Brain/ListKnowledge.php create mode 100644 Actions/Brain/RecallKnowledge.php create mode 100644 Actions/Brain/RememberKnowledge.php create mode 100644 Controllers/Api/BrainController.php rename {routes => Routes}/admin.php (100%) create mode 100644 Routes/api.php rename {routes => Routes}/console.php (100%) rename {routes => Routes}/web.php (100%) delete mode 100644 database/migrations/.gitkeep delete mode 100644 routes/api.php diff --git a/Actions/Brain/ForgetKnowledge.php b/Actions/Brain/ForgetKnowledge.php new file mode 100644 index 0000000..c150093 --- /dev/null +++ b/Actions/Brain/ForgetKnowledge.php @@ -0,0 +1,62 @@ +where('workspace_id', $workspaceId) + ->first(); + + if (! $memory) { + throw new \InvalidArgumentException("Memory '{$id}' not found in this workspace"); + } + + Log::info('OpenBrain: memory forgotten', [ + 'id' => $id, + 'type' => $memory->type, + 'agent_id' => $agentId, + 'reason' => $reason, + ]); + + $this->brain->forget($id); + + return [ + 'forgotten' => $id, + 'type' => $memory->type, + ]; + } +} diff --git a/Actions/Brain/ListKnowledge.php b/Actions/Brain/ListKnowledge.php new file mode 100644 index 0000000..e4c0e94 --- /dev/null +++ b/Actions/Brain/ListKnowledge.php @@ -0,0 +1,57 @@ + 'decision']); + */ +class ListKnowledge +{ + use Action; + + /** + * @param array{project?: string, type?: string, agent_id?: string, limit?: int} $filter + * @return array{memories: array, count: int} + */ + public function handle(int $workspaceId, array $filter = []): array + { + $limit = min(max((int) ($filter['limit'] ?? 20), 1), 100); + + $query = BrainMemory::forWorkspace($workspaceId) + ->active() + ->latestVersions() + ->forProject($filter['project'] ?? null) + ->byAgent($filter['agent_id'] ?? null); + + $type = $filter['type'] ?? null; + if ($type !== null) { + if (is_string($type) && ! in_array($type, BrainMemory::VALID_TYPES, true)) { + throw new \InvalidArgumentException( + sprintf('type must be one of: %s', implode(', ', BrainMemory::VALID_TYPES)) + ); + } + $query->ofType($type); + } + + $memories = $query->orderByDesc('created_at') + ->limit($limit) + ->get(); + + return [ + 'memories' => $memories->map(fn (BrainMemory $m) => $m->toMcpContext())->all(), + 'count' => $memories->count(), + ]; + } +} diff --git a/Actions/Brain/RecallKnowledge.php b/Actions/Brain/RecallKnowledge.php new file mode 100644 index 0000000..63c9b2e --- /dev/null +++ b/Actions/Brain/RecallKnowledge.php @@ -0,0 +1,85 @@ +, count: int} + * + * @throws \InvalidArgumentException + * @throws \RuntimeException + */ + public function handle(string $query, int $workspaceId, array $filter = [], int $topK = 5): array + { + if ($query === '') { + throw new \InvalidArgumentException('query is required and must be a non-empty string'); + } + if (mb_strlen($query) > 2000) { + throw new \InvalidArgumentException('query must not exceed 2,000 characters'); + } + + if ($topK < 1 || $topK > 20) { + throw new \InvalidArgumentException('top_k must be between 1 and 20'); + } + + if (isset($filter['type'])) { + $typeValue = $filter['type']; + $validTypes = BrainMemory::VALID_TYPES; + + if (is_string($typeValue)) { + if (! in_array($typeValue, $validTypes, true)) { + throw new \InvalidArgumentException( + sprintf('filter.type must be one of: %s', implode(', ', $validTypes)) + ); + } + } elseif (is_array($typeValue)) { + foreach ($typeValue as $t) { + if (! is_string($t) || ! in_array($t, $validTypes, true)) { + throw new \InvalidArgumentException( + sprintf('Each filter.type value must be one of: %s', implode(', ', $validTypes)) + ); + } + } + } + } + + if (isset($filter['min_confidence'])) { + $mc = $filter['min_confidence']; + if (! is_numeric($mc) || $mc < 0.0 || $mc > 1.0) { + throw new \InvalidArgumentException('filter.min_confidence must be between 0.0 and 1.0'); + } + } + + $result = $this->brain->recall($query, $topK, $filter, $workspaceId); + + return [ + 'memories' => $result['memories'], + 'scores' => $result['scores'], + 'count' => count($result['memories']), + ]; + } +} diff --git a/Actions/Brain/RememberKnowledge.php b/Actions/Brain/RememberKnowledge.php new file mode 100644 index 0000000..aed0f20 --- /dev/null +++ b/Actions/Brain/RememberKnowledge.php @@ -0,0 +1,94 @@ + 50000) { + throw new \InvalidArgumentException('content must not exceed 50,000 characters'); + } + + $type = $data['type'] ?? null; + if (! is_string($type) || ! in_array($type, BrainMemory::VALID_TYPES, true)) { + throw new \InvalidArgumentException( + sprintf('type must be one of: %s', implode(', ', BrainMemory::VALID_TYPES)) + ); + } + + $confidence = (float) ($data['confidence'] ?? 0.8); + if ($confidence < 0.0 || $confidence > 1.0) { + throw new \InvalidArgumentException('confidence must be between 0.0 and 1.0'); + } + + $tags = $data['tags'] ?? null; + if (is_array($tags)) { + foreach ($tags as $tag) { + if (! is_string($tag)) { + throw new \InvalidArgumentException('Each tag must be a string'); + } + } + } + + $supersedes = $data['supersedes'] ?? null; + if ($supersedes !== null) { + $existing = BrainMemory::where('id', $supersedes) + ->where('workspace_id', $workspaceId) + ->first(); + + if (! $existing) { + throw new \InvalidArgumentException("Memory '{$supersedes}' not found in this workspace"); + } + } + + $expiresIn = isset($data['expires_in']) ? (int) $data['expires_in'] : null; + if ($expiresIn !== null && $expiresIn < 1) { + throw new \InvalidArgumentException('expires_in must be at least 1 hour'); + } + + return $this->brain->remember([ + 'workspace_id' => $workspaceId, + 'agent_id' => $agentId, + 'type' => $type, + 'content' => $content, + 'tags' => $tags, + 'project' => $data['project'] ?? null, + 'confidence' => $confidence, + 'supersedes_id' => $supersedes, + 'expires_at' => $expiresIn ? now()->addHours($expiresIn) : null, + ]); + } +} diff --git a/Boot.php b/Boot.php index 1679f2f..21f5b39 100644 --- a/Boot.php +++ b/Boot.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Core\Mod\Agentic; use Core\Events\AdminPanelBooting; +use Core\Events\ApiRoutesRegistering; use Core\Events\ConsoleBooting; use Core\Events\McpToolsRegistering; use Illuminate\Cache\RateLimiting\Limit; @@ -24,6 +25,7 @@ class Boot extends ServiceProvider */ public static array $listens = [ AdminPanelBooting::class => 'onAdminPanel', + ApiRoutesRegistering::class => 'onApiRoutes', ConsoleBooting::class => 'onConsole', McpToolsRegistering::class => 'onMcpTools', ]; @@ -134,6 +136,14 @@ class Boot extends ServiceProvider // in the existing boot() method until we migrate to pure event-driven nav. } + /** + * Handle API routes registration event. + */ + public function onApiRoutes(ApiRoutesRegistering $event): void + { + $event->routes(fn () => require __DIR__.'/Routes/api.php'); + } + /** * Handle console booting event. */ diff --git a/Controllers/Api/BrainController.php b/Controllers/Api/BrainController.php new file mode 100644 index 0000000..bbc2c28 --- /dev/null +++ b/Controllers/Api/BrainController.php @@ -0,0 +1,164 @@ +validate([ + 'content' => 'required|string|max:50000', + 'type' => 'required|string', + 'tags' => 'nullable|array', + 'tags.*' => 'string', + 'project' => 'nullable|string|max:255', + 'confidence' => 'nullable|numeric|min:0|max:1', + 'supersedes' => 'nullable|uuid', + 'expires_in' => 'nullable|integer|min:1', + ]); + + $workspace = $request->attributes->get('workspace'); + $apiKey = $request->attributes->get('api_key'); + $agentId = $apiKey?->name ?? 'api'; + + try { + $memory = RememberKnowledge::run($validated, $workspace->id, $agentId); + + return response()->json([ + 'data' => $memory->toMcpContext(), + ], 201); + } catch (\InvalidArgumentException $e) { + return response()->json([ + 'error' => 'validation_error', + 'message' => $e->getMessage(), + ], 422); + } catch (\RuntimeException $e) { + return response()->json([ + 'error' => 'service_error', + 'message' => 'Brain service temporarily unavailable.', + ], 503); + } + } + + /** + * POST /api/brain/recall + * + * Semantic search across memories. + */ + public function recall(Request $request): JsonResponse + { + $validated = $request->validate([ + 'query' => 'required|string|max:2000', + 'top_k' => 'nullable|integer|min:1|max:20', + 'filter' => 'nullable|array', + 'filter.project' => 'nullable|string', + 'filter.type' => 'nullable', + 'filter.agent_id' => 'nullable|string', + 'filter.min_confidence' => 'nullable|numeric|min:0|max:1', + ]); + + $workspace = $request->attributes->get('workspace'); + + try { + $result = RecallKnowledge::run( + $validated['query'], + $workspace->id, + $validated['filter'] ?? [], + $validated['top_k'] ?? 5, + ); + + return response()->json([ + 'data' => $result, + ]); + } catch (\InvalidArgumentException $e) { + return response()->json([ + 'error' => 'validation_error', + 'message' => $e->getMessage(), + ], 422); + } catch (\RuntimeException $e) { + return response()->json([ + 'error' => 'service_error', + 'message' => 'Brain service temporarily unavailable.', + ], 503); + } + } + + /** + * DELETE /api/brain/forget/{id} + * + * Remove a memory. + */ + public function forget(Request $request, string $id): JsonResponse + { + $request->validate([ + 'reason' => 'nullable|string|max:500', + ]); + + $workspace = $request->attributes->get('workspace'); + $apiKey = $request->attributes->get('api_key'); + $agentId = $apiKey?->name ?? 'api'; + + try { + $result = ForgetKnowledge::run($id, $workspace->id, $agentId, $request->input('reason')); + + return response()->json([ + 'data' => $result, + ]); + } catch (\InvalidArgumentException $e) { + return response()->json([ + 'error' => 'not_found', + 'message' => $e->getMessage(), + ], 404); + } catch (\RuntimeException $e) { + return response()->json([ + 'error' => 'service_error', + 'message' => 'Brain service temporarily unavailable.', + ], 503); + } + } + + /** + * GET /api/brain/list + * + * List memories with optional filters. + */ + public function list(Request $request): JsonResponse + { + $validated = $request->validate([ + 'project' => 'nullable|string', + 'type' => 'nullable|string', + 'agent_id' => 'nullable|string', + 'limit' => 'nullable|integer|min:1|max:100', + ]); + + $workspace = $request->attributes->get('workspace'); + + try { + $result = ListKnowledge::run($workspace->id, $validated); + + return response()->json([ + 'data' => $result, + ]); + } catch (\InvalidArgumentException $e) { + return response()->json([ + 'error' => 'validation_error', + 'message' => $e->getMessage(), + ], 422); + } + } +} diff --git a/Mcp/Tools/Agent/Brain/BrainForget.php b/Mcp/Tools/Agent/Brain/BrainForget.php index 8cb1d7e..6f3cafb 100644 --- a/Mcp/Tools/Agent/Brain/BrainForget.php +++ b/Mcp/Tools/Agent/Brain/BrainForget.php @@ -5,10 +5,9 @@ declare(strict_types=1); namespace Core\Mod\Agentic\Mcp\Tools\Agent\Brain; use Core\Mcp\Dependencies\ToolDependency; +use Core\Mod\Agentic\Actions\Brain\ForgetKnowledge; use Core\Mod\Agentic\Mcp\Tools\Agent\AgentTool; use Core\Mod\Agentic\Models\BrainMemory; -use Core\Mod\Agentic\Services\BrainService; -use Illuminate\Support\Facades\Log; /** * Remove a memory from the shared OpenBrain knowledge store. @@ -61,43 +60,19 @@ class BrainForget extends AgentTool public function handle(array $args, array $context = []): array { - try { - $id = $this->requireString($args, 'id'); - } catch (\InvalidArgumentException $e) { - return $this->error($e->getMessage()); - } - $workspaceId = $context['workspace_id'] ?? null; if ($workspaceId === null) { return $this->error('workspace_id is required. Ensure you have authenticated with a valid API key. See: https://host.uk.com/ai'); } + $id = $args['id'] ?? ''; $reason = $this->optionalString($args, 'reason', null, 500); $agentId = $context['agent_id'] ?? $context['session_id'] ?? 'anonymous'; - return $this->withCircuitBreaker('brain', function () use ($id, $workspaceId, $reason, $agentId) { - // Verify memory exists and belongs to this workspace - $memory = BrainMemory::where('id', $id) - ->where('workspace_id', $workspaceId) - ->first(); + return $this->withCircuitBreaker('brain', function () use ($id, $workspaceId, $agentId, $reason) { + $result = ForgetKnowledge::run($id, (int) $workspaceId, $agentId, $reason); - if (! $memory) { - return $this->error("Memory '{$id}' not found in this workspace"); - } - - Log::info('OpenBrain: memory forgotten', [ - 'id' => $id, - 'type' => $memory->type, - 'agent_id' => $agentId, - 'reason' => $reason, - ]); - - app(BrainService::class)->forget($id); - - return $this->success([ - 'forgotten' => $id, - 'type' => $memory->type, - ]); + return $this->success($result); }, fn () => $this->error('Brain service temporarily unavailable. Memory could not be removed.', 'service_unavailable')); } } diff --git a/Mcp/Tools/Agent/Brain/BrainList.php b/Mcp/Tools/Agent/Brain/BrainList.php index a9daa0a..bffaf6e 100644 --- a/Mcp/Tools/Agent/Brain/BrainList.php +++ b/Mcp/Tools/Agent/Brain/BrainList.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Core\Mod\Agentic\Mcp\Tools\Agent\Brain; use Core\Mcp\Dependencies\ToolDependency; +use Core\Mod\Agentic\Actions\Brain\ListKnowledge; use Core\Mod\Agentic\Mcp\Tools\Agent\AgentTool; use Core\Mod\Agentic\Models\BrainMemory; @@ -73,28 +74,8 @@ class BrainList extends AgentTool return $this->error('workspace_id is required. Ensure you have authenticated with a valid API key. See: https://host.uk.com/ai'); } - $project = $this->optionalString($args, 'project'); - $type = $this->optionalEnum($args, 'type', BrainMemory::VALID_TYPES); - $agentId = $this->optionalString($args, 'agent_id'); - $limit = $this->optionalInt($args, 'limit', 20, 1, 100); + $result = ListKnowledge::run((int) $workspaceId, $args); - $query = BrainMemory::forWorkspace((int) $workspaceId) - ->active() - ->latestVersions() - ->forProject($project) - ->byAgent($agentId); - - if ($type !== null) { - $query->ofType($type); - } - - $memories = $query->orderByDesc('created_at') - ->limit($limit) - ->get(); - - return $this->success([ - 'count' => $memories->count(), - 'memories' => $memories->map(fn (BrainMemory $m) => $m->toMcpContext())->all(), - ]); + return $this->success($result); } } diff --git a/Mcp/Tools/Agent/Brain/BrainRecall.php b/Mcp/Tools/Agent/Brain/BrainRecall.php index 84eb8bd..f2b67fd 100644 --- a/Mcp/Tools/Agent/Brain/BrainRecall.php +++ b/Mcp/Tools/Agent/Brain/BrainRecall.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace Core\Mod\Agentic\Mcp\Tools\Agent\Brain; use Core\Mcp\Dependencies\ToolDependency; +use Core\Mod\Agentic\Actions\Brain\RecallKnowledge; use Core\Mod\Agentic\Mcp\Tools\Agent\AgentTool; use Core\Mod\Agentic\Models\BrainMemory; -use Core\Mod\Agentic\Services\BrainService; /** * Semantic search across the shared OpenBrain knowledge store. @@ -93,17 +93,12 @@ class BrainRecall extends AgentTool public function handle(array $args, array $context = []): array { - try { - $query = $this->requireString($args, 'query', 2000); - } catch (\InvalidArgumentException $e) { - return $this->error($e->getMessage()); - } - $workspaceId = $context['workspace_id'] ?? null; if ($workspaceId === null) { return $this->error('workspace_id is required. Ensure you have authenticated with a valid API key. See: https://host.uk.com/ai'); } + $query = $args['query'] ?? ''; $topK = $this->optionalInt($args, 'top_k', 5, 1, 20); $filter = $this->optional($args, 'filter', []); @@ -111,37 +106,11 @@ class BrainRecall extends AgentTool return $this->error('filter must be an object'); } - // Validate filter type values if present - if (isset($filter['type'])) { - $typeValue = $filter['type']; - $validTypes = BrainMemory::VALID_TYPES; - - if (is_string($typeValue)) { - if (! in_array($typeValue, $validTypes, true)) { - return $this->error(sprintf('filter.type must be one of: %s', implode(', ', $validTypes))); - } - } elseif (is_array($typeValue)) { - foreach ($typeValue as $t) { - if (! is_string($t) || ! in_array($t, $validTypes, true)) { - return $this->error(sprintf('Each filter.type value must be one of: %s', implode(', ', $validTypes))); - } - } - } else { - return $this->error('filter.type must be a string or array of strings'); - } - } - - if (isset($filter['min_confidence'])) { - if (! is_numeric($filter['min_confidence']) || $filter['min_confidence'] < 0.0 || $filter['min_confidence'] > 1.0) { - return $this->error('filter.min_confidence must be a number between 0.0 and 1.0'); - } - } - - return $this->withCircuitBreaker('brain', function () use ($query, $topK, $filter, $workspaceId) { - $result = app(BrainService::class)->recall($query, $topK, $filter, (int) $workspaceId); + return $this->withCircuitBreaker('brain', function () use ($query, $workspaceId, $filter, $topK) { + $result = RecallKnowledge::run($query, (int) $workspaceId, $filter, $topK); return $this->success([ - 'count' => count($result['memories']), + 'count' => $result['count'], 'memories' => $result['memories'], 'scores' => $result['scores'], ]); diff --git a/Mcp/Tools/Agent/Brain/BrainRemember.php b/Mcp/Tools/Agent/Brain/BrainRemember.php index a01ec11..9cc84a2 100644 --- a/Mcp/Tools/Agent/Brain/BrainRemember.php +++ b/Mcp/Tools/Agent/Brain/BrainRemember.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace Core\Mod\Agentic\Mcp\Tools\Agent\Brain; use Core\Mcp\Dependencies\ToolDependency; +use Core\Mod\Agentic\Actions\Brain\RememberKnowledge; use Core\Mod\Agentic\Mcp\Tools\Agent\AgentTool; use Core\Mod\Agentic\Models\BrainMemory; -use Core\Mod\Agentic\Services\BrainService; /** * Store a memory in the shared OpenBrain knowledge store. @@ -85,62 +85,15 @@ class BrainRemember extends AgentTool public function handle(array $args, array $context = []): array { - try { - $content = $this->requireString($args, 'content', 50000); - $type = $this->requireEnum($args, 'type', BrainMemory::VALID_TYPES); - } catch (\InvalidArgumentException $e) { - return $this->error($e->getMessage()); - } - $workspaceId = $context['workspace_id'] ?? null; if ($workspaceId === null) { return $this->error('workspace_id is required. Ensure you have authenticated with a valid API key. See: https://host.uk.com/ai'); } $agentId = $context['agent_id'] ?? $context['session_id'] ?? 'anonymous'; - $tags = $this->optional($args, 'tags'); - $project = $this->optionalString($args, 'project'); - $supersedes = $this->optionalString($args, 'supersedes'); - $expiresIn = $this->optionalInt($args, 'expires_in', null, 1); - $confidence = $this->optional($args, 'confidence', 0.8); - if (! is_numeric($confidence) || $confidence < 0.0 || $confidence > 1.0) { - return $this->error('confidence must be a number between 0.0 and 1.0'); - } - $confidence = (float) $confidence; - - if (is_array($tags)) { - foreach ($tags as $tag) { - if (! is_string($tag)) { - return $this->error('Each tag must be a string'); - } - } - } - - if ($supersedes !== null) { - $existing = BrainMemory::where('id', $supersedes) - ->where('workspace_id', $workspaceId) - ->first(); - - if (! $existing) { - return $this->error("Memory '{$supersedes}' not found in this workspace"); - } - } - - return $this->withCircuitBreaker('brain', function () use ( - $content, $type, $workspaceId, $agentId, $tags, $project, $confidence, $supersedes, $expiresIn - ) { - $memory = app(BrainService::class)->remember([ - 'workspace_id' => $workspaceId, - 'agent_id' => $agentId, - 'type' => $type, - 'content' => $content, - 'tags' => $tags, - 'project' => $project, - 'confidence' => $confidence, - 'supersedes_id' => $supersedes, - 'expires_at' => $expiresIn ? now()->addHours($expiresIn) : null, - ]); + return $this->withCircuitBreaker('brain', function () use ($args, $workspaceId, $agentId) { + $memory = RememberKnowledge::run($args, (int) $workspaceId, $agentId); return $this->success([ 'memory' => $memory->toMcpContext(), diff --git a/routes/admin.php b/Routes/admin.php similarity index 100% rename from routes/admin.php rename to Routes/admin.php diff --git a/Routes/api.php b/Routes/api.php new file mode 100644 index 0000000..2aff966 --- /dev/null +++ b/Routes/api.php @@ -0,0 +1,27 @@ +prefix('brain') + ->name('brain.') + ->group(function () { + Route::post('remember', [BrainController::class, 'remember'])->name('remember'); + Route::post('recall', [BrainController::class, 'recall'])->name('recall'); + Route::delete('forget/{id}', [BrainController::class, 'forget'])->name('forget') + ->where('id', '[0-9a-f-]+'); + Route::get('list', [BrainController::class, 'list'])->name('list'); + }); diff --git a/routes/console.php b/Routes/console.php similarity index 100% rename from routes/console.php rename to Routes/console.php diff --git a/routes/web.php b/Routes/web.php similarity index 100% rename from routes/web.php rename to Routes/web.php diff --git a/database/migrations/.gitkeep b/database/migrations/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/routes/api.php b/routes/api.php deleted file mode 100644 index 9552cfe..0000000 --- a/routes/api.php +++ /dev/null @@ -1,3 +0,0 @@ -