<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jorge Peraza</title>
    <description>The latest articles on DEV Community by Jorge Peraza (@yorchperaza).</description>
    <link>https://dev.to/yorchperaza</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F523500%2F0e1b9593-4b5e-4f86-bf86-72a2a8ac5c0d.jpg</url>
      <title>DEV Community: Jorge Peraza</title>
      <link>https://dev.to/yorchperaza</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yorchperaza"/>
    <language>en</language>
    <item>
      <title>Why 46% of Teams Ship Failing AI Code (And How to Fix It)</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Mon, 12 Oct 2026 14:10:08 +0000</pubDate>
      <link>https://dev.to/yorchperaza/why-46-of-teams-ship-failing-ai-code-and-how-to-fix-it-3j7d</link>
      <guid>https://dev.to/yorchperaza/why-46-of-teams-ship-failing-ai-code-and-how-to-fix-it-3j7d</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A survey published by SmartBear on October 2, 2026, reveals that 46% of engineering teams have shipped AI-generated code that subsequently failed in production.&lt;/li&gt;
&lt;li&gt;Software engineers report that top-down executive pressure to adopt AI tools has created a surge in bug volumes and unmanageable manual verification workloads.&lt;/li&gt;
&lt;li&gt;Single-shot AI completions shift the burden of edge-case verification directly onto the developer during the Pull Request phase.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://monkeyscode.com" rel="noopener noreferrer"&gt;MonkeysCode&lt;/a&gt; addresses this structural flaw through its desktop editor (v1.2.12) and built-in &lt;a href="https://monkeyscode.com/capuchin" rel="noopener noreferrer"&gt;Capuchin&lt;/a&gt; agent, which executes automated test suites and iterates on failures before code is finalized.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Verification Trap of Raw Completions
&lt;/h2&gt;

&lt;p&gt;On October 2, 2026, SmartBear released a survey of nearly 1,500 developers and engineering leaders detailing the downstream effects of AI adoption. The most glaring data point: 46% of engineering teams have shipped AI-generated code that failed in production. &lt;/p&gt;

&lt;p&gt;The report highlights a growing friction point in modern software development. Executives, eager for efficiency gains, apply top-down pressure on engineering organizations to adopt AI coding tools. However, most first-generation AI assistants operate as single-shot generators. They emit raw code completions based on static context without any mechanism to verify if the code compiles, let alone if it satisfies business logic or handles edge cases.&lt;/p&gt;

&lt;p&gt;The result is a predictable shift in bottlenecks. Time saved typing boilerplate is immediately lost to an unmanageable manual verification workload. Developers inherit a massive PR review burden, forced to act as human linters for models that confidently hallucinate API contracts or overlook complex systemic dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Single-Shot AI Fails in Complex Systems
&lt;/h2&gt;

&lt;p&gt;Large Language Models (LLMs) predict the most statistically probable next tokens based on the prompt provided. When generating a function, a single-shot model does not have access to a runtime environment. It cannot run a compiler, it cannot execute a unit test, and it cannot observe the actual state of a database schema at runtime.&lt;/p&gt;

&lt;p&gt;Consider a recent real-world scenario from the MonkeysCode platform development. When querying a Postgres database for a JSONB field, a developer might naturally use the &lt;code&gt;?&lt;/code&gt; operator. A standard AI autocomplete tool will likely suggest exactly that. However, depending on the specific Postgres driver or ORM configuration, the &lt;code&gt;?&lt;/code&gt; operator can conflict with parameterized query syntax, causing a runtime failure. The correct approach in that specific environment might be to use the &lt;code&gt;jsonb_exists()&lt;/code&gt; function instead (a fix we recently shipped to our own platform on October 4).&lt;/p&gt;

&lt;p&gt;A single-shot AI tool has no way of knowing this will fail until the developer runs the code, encounters the error, and manually corrects it. If the developer skips local testing—perhaps trusting the AI's output under deadline pressure—the bug ships to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating Verification with Capuchin
&lt;/h2&gt;

&lt;p&gt;To counter the manual verification burden, the AI tooling architecture must evolve from static generation to dynamic iteration. The machine must take responsibility for the verification loop.&lt;/p&gt;

&lt;p&gt;This is the engineering philosophy behind &lt;a href="https://monkeyscode.com" rel="noopener noreferrer"&gt;MonkeysCode&lt;/a&gt;, an AI coding platform designed around the &lt;a href="https://monkeyscode.com/capuchin" rel="noopener noreferrer"&gt;Capuchin agent&lt;/a&gt;. Built directly into the MonkeysCode desktop IDE, Capuchin does not just emit raw text completions. Instead, it operates on a structured loop: plan, modify across multiple files, execute tests, and iterate.&lt;/p&gt;

&lt;p&gt;With the release of MonkeysCode editor v1.2.12 and Agent Manager 1.0.16 (shipped October 5, 2026), this iterative workflow is deeply integrated into the local environment. When a developer assigns a task to Capuchin, the agent can run the local test suite using the MonkeysCode CLI. If a test fails, the agent reads the &lt;code&gt;stdout&lt;/code&gt;/&lt;code&gt;stderr&lt;/code&gt; output, analyzes the stack trace, and modifies the code to resolve the regression before presenting the final changes to the developer.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Iterative Loop in Practice
&lt;/h3&gt;

&lt;p&gt;Here is a conceptual look at how this loop functions within the MonkeysCode desktop IDE:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Context Assembly:&lt;/strong&gt; The developer provides a prompt. The Context Kernel gathers relevant file context. To manage token costs efficiently during complex tasks, we separate system blocks for models like GPT-6 and GPT-5.6 to maximize cache breakpoints (a feature refined in our recent GPT-6 model refresh).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Multi-File Execution:&lt;/strong&gt; Capuchin plans and executes edits across multiple files simultaneously.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Test Execution:&lt;/strong&gt; Capuchin runs the designated test command (e.g., &lt;code&gt;npm run test:auth&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Error Consumption:&lt;/strong&gt; If a test fails, Capuchin consumes the error output.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Iteration:&lt;/strong&gt; The agent writes a patch to fix the error and re-runs the test.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In version 1.2.11 of the editor, we shipped UI updates to make supervising this loop seamless. Markdown test output, including tables, now renders directly in the chat interface. Furthermore, file names on diff cards are fully interactive—clicking them immediately opens the corresponding file tab so the developer can inspect the agent's work in context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example of Capuchin's internal iterative logging&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;Agent] Executing modifications &lt;span class="k"&gt;in &lt;/span&gt;src/db/queries.ts
&lt;span class="o"&gt;[&lt;/span&gt;Agent] Running &lt;span class="nb"&gt;test command&lt;/span&gt;: npm &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"should fetch user metadata"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;System] Test failed: syntax error at or near &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;Agent] Analyzing failure: Postgres parameterization conflict with &lt;span class="s1"&gt;'?'&lt;/span&gt; operator.
&lt;span class="o"&gt;[&lt;/span&gt;Agent] Modifying query to use jsonb_exists&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;Agent] Re-running &lt;span class="nb"&gt;test &lt;/span&gt;command...
&lt;span class="o"&gt;[&lt;/span&gt;System] Test passed.
&lt;span class="o"&gt;[&lt;/span&gt;Agent] Presenting diff to user.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Managing the Trade-Offs
&lt;/h2&gt;

&lt;p&gt;It is important to acknowledge the trade-offs of this architecture. An iterative, test-driven agent takes longer to complete a task than a single-shot autocomplete tool. It requires keeping connections alive while the model "thinks" (our model-proxy actively keeps Anthropic SSE streams alive to handle these extended reasoning phases). &lt;/p&gt;

&lt;p&gt;Furthermore, running multiple iterations consumes more tokens. To mitigate this, MonkeysCode leans heavily on prompt caching. Our newly shipped Auto mode tab on the Models &amp;amp; Pricing page allows teams to monitor cache ratios and warns on mixed frontier providers to ensure maximum efficiency. We also recently updated the usage dashboards to explicitly show cache-write tokens, giving engineering managers full visibility into the cost of automated verification.&lt;/p&gt;

&lt;p&gt;However, the time spent waiting for an agent to run tests locally is time the developer gets back during the PR review phase. By catching hallucinations and regressions in the local environment, the volume of bugs that reach production drops significantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;The SmartBear data confirms what senior engineers already know: adopting AI tools without adapting your verification workflows is a recipe for production regressions. Executive pressure to ship faster cannot override the fundamental requirement for code correctness. &lt;/p&gt;

&lt;p&gt;AI coding tools must evolve to integrate deeply with existing testing infrastructure. By moving the test-and-iterate loop inside the agent itself, tools like MonkeysCode shift the verification burden away from the developer, ensuring that the code you review is code that actually works.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;To explore automated test-and-iterate workflows, &lt;a href="https://monkeyscode.com/download" rel="noopener noreferrer"&gt;download the MonkeysCode editor&lt;/a&gt; or read the &lt;a href="https://monkeyscode.com/docs" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aicoding</category>
      <category>engineeringmanagement</category>
      <category>softwaretesting</category>
      <category>capuchin</category>
    </item>
    <item>
      <title>Preventing AI Lock-in: Open Models and Auto Routing</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Sun, 11 Oct 2026 14:10:08 +0000</pubDate>
      <link>https://dev.to/yorchperaza/preventing-ai-lock-in-open-models-and-auto-routing-2o7e</link>
      <guid>https://dev.to/yorchperaza/preventing-ai-lock-in-open-models-and-auto-routing-2o7e</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On October 2, 2026, Shakudo published a guide highlighting a shift toward open agents and open-weight models to prevent commercial vendor lock-in during repository refactoring.&lt;/li&gt;
&lt;li&gt;MonkeysCode decouples agentic capabilities from specific providers by natively supporting Bring Your Own Key (BYOK), local models, and open-weight models via Capuchin.&lt;/li&gt;
&lt;li&gt;With the release of CLI 1.0.2, Agent Manager 1.0.16, and Editor 1.2.12, developers can execute multi-provider agent runs across their codebase.&lt;/li&gt;
&lt;li&gt;A new "Auto" routing tab in the Models &amp;amp; Pricing dashboard tracks cache ratios and actively warns when mixing frontier providers degrades prompt caching efficiency.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Vendor Lock-In Problem
&lt;/h2&gt;

&lt;p&gt;When adopting AI coding assistants for large-scale repository refactoring, engineering teams face a growing architectural risk: tight coupling to a single proprietary model provider. &lt;/p&gt;

&lt;p&gt;On October 2, 2026, Shakudo published a comparative guide analyzing coding assistants like OpenHands, Cline, and Goose. The core finding was clear: development teams are increasingly leveraging open agents alongside self-hosted and open-weight models to perform complex, multi-file refactoring. The motivation is straightforward. Relying exclusively on a single commercial vendor exposes teams to unpredictable API pricing shifts, arbitrary rate limit changes, and restrictive data governance policies. If an agent's capability is hardcoded to a specific frontier model's API, migrating away from that provider means losing the agent's multi-file capabilities.&lt;/p&gt;

&lt;p&gt;Developers want the freedom to choose their infrastructure. They need the ability to run open-weight models locally for sensitive modules, while dynamically routing complex planning tasks to frontier models—all without sacrificing the agentic workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decoupling the Agent from the API
&lt;/h2&gt;

&lt;p&gt;At MonkeysCode, we believe your codebase should not be held hostage by your choice of model provider. &lt;a href="https://monkeyscode.com/capuchin" rel="noopener noreferrer"&gt;Capuchin&lt;/a&gt;, our built-in coding agent, is designed as an abstraction layer that separates the agent's planning, testing, and editing logic from the underlying LLM.&lt;/p&gt;

&lt;p&gt;This week, we released Editor 1.2.12, Agent Manager 1.0.16, and CLI 1.0.2, which solidify our support for multi-provider agent runs. You can utilize Capuchin to plan and execute codebase-wide edits while completely controlling the model backend. MonkeysCode supports:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Bring Your Own Key (BYOK):&lt;/strong&gt; Connect directly to frontier models using your own API keys, ensuring your usage and billing remain under your control.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Open Models:&lt;/strong&gt; Integrate with hosted open-weight models via compatible endpoints.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Local Models:&lt;/strong&gt; Point Capuchin to a local inference server running on your own hardware, ensuring zero code leaves your machine.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By keeping the agent logic client-side within the &lt;a href="https://monkeyscode.com/features" rel="noopener noreferrer"&gt;MonkeysCode editor&lt;/a&gt; and &lt;a href="https://monkeyscode.com/docs" rel="noopener noreferrer"&gt;CLI&lt;/a&gt;, you retain the ability to refactor safely without commercial lock-in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring Local and Open-Weight Models
&lt;/h2&gt;

&lt;p&gt;Setting up Capuchin to use a local or self-hosted model is done via standard configuration. Because the MonkeysCode Agent Manager standardizes the communication protocol, you can point the agent to any compatible local endpoint.&lt;/p&gt;

&lt;p&gt;Here is an example of how you might configure your workspace to route Capuchin's requests to a local inference server running an open-weight model on &lt;code&gt;localhost&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"models"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local-open-weight"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"providers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"local-open-weight"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"custom"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://127.0.0.1:8080/v1/chat/completions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"context_window"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;32000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"require_authentication"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"auto_route"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once configured, invoking the agent via the CLI seamlessly uses your local hardware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start a refactoring task using the local model&lt;/span&gt;
monkeyscode agent run &lt;span class="nt"&gt;--task&lt;/span&gt; &lt;span class="s2"&gt;"Refactor the authentication middleware to use the new session store"&lt;/span&gt; &lt;span class="nt"&gt;--model&lt;/span&gt; local-open-weight
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This workflow ensures that even if a commercial API experiences downtime or changes its pricing structure, your team can continue operating by switching to an open-weight alternative.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto Routing and the Prompt Caching Trade-off
&lt;/h2&gt;

&lt;p&gt;While local models are excellent for data privacy and cost control, large-scale repository refactoring often benefits from routing highly complex planning tasks to frontier models. MonkeysCode supports "Auto" model routing, which dynamically selects the best model for a given sub-task.&lt;/p&gt;

&lt;p&gt;However, mixing providers introduces a technical challenge: &lt;strong&gt;prompt caching&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When Capuchin reads your codebase, it loads significant context into the model's memory. Modern frontier APIs use prompt caching to reduce the cost and latency of subsequent requests that share the same context prefix. If Auto routing switches a task from Provider A to Provider B mid-execution, the cache context is lost. Provider B must ingest the entire codebase context from scratch, leading to increased token usage and higher latency.&lt;/p&gt;

&lt;p&gt;To surface this trade-off, we shipped an update to the Models &amp;amp; Pricing dashboard on October 6, 2026. The new &lt;strong&gt;Auto&lt;/strong&gt; tab explicitly tracks your cache ratios (including cache-write tokens) and actively warns you when mixed frontier providers are degrading your prompt caching efficiency.&lt;/p&gt;

&lt;p&gt;If you are using Auto mode across multiple providers, the dashboard will now display an alert similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WARNING: Mixed Provider Caching Degradation
Auto routing has distributed tasks between Provider A and Provider B.
Cache hit ratio has dropped by 42% due to context invalidation across endpoints.
Recommendation: Pin repository context to a single provider for the duration of the agent session.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This visibility allows engineering teams to make informed decisions. You can choose to optimize for capability by mixing models, or optimize for cost and speed by pinning a session to a single provider to maximize cache hits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware and Latency Considerations
&lt;/h2&gt;

&lt;p&gt;While the architectural freedom to use local and open-weight models is a significant advantage, it is important to acknowledge the practical trade-offs. &lt;/p&gt;

&lt;p&gt;Currently, MonkeysCode does not publish documented memory or VRAM hardware requirements for running specific open-weight models locally. The performance of local inference is heavily dependent on your specific hardware configuration. Furthermore, we do not provide side-by-side performance and latency benchmarks comparing local Capuchin runs against frontier cloud APIs for repository refactoring. &lt;/p&gt;

&lt;p&gt;Teams opting for local models must evaluate their own hardware constraints and test the latency of local inference against their specific refactoring workloads. If local latency becomes a bottleneck, the BYOK architecture allows you to easily pivot back to a hosted open-weight endpoint or a frontier model without changing your Capuchin workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Vendor lock-in is a valid concern for engineering teams adopting AI tools, as highlighted by Shakudo's recent findings. Tying your development workflows to a single proprietary API limits flexibility and exposes you to external risks. &lt;/p&gt;

&lt;p&gt;By decoupling the agent from the model, MonkeysCode ensures that Capuchin works seamlessly across frontier APIs via BYOK, hosted open-weight endpoints, and local models. With the new Auto routing dashboard providing transparent metrics on prompt caching, teams have the data they need to balance capability, cost, and independence.&lt;/p&gt;

&lt;p&gt;Ready to regain control over your AI infrastructure? &lt;a href="https://monkeyscode.com/download" rel="noopener noreferrer"&gt;Download MonkeysCode&lt;/a&gt; today or explore our &lt;a href="https://monkeyscode.com/for-teams" rel="noopener noreferrer"&gt;Teams plans&lt;/a&gt; for organization-wide deployment.&lt;/p&gt;

</description>
      <category>vendorlockin</category>
      <category>openmodels</category>
      <category>promptcaching</category>
      <category>capuchin</category>
    </item>
    <item>
      <title>Handling Frontier Model Churn: GPT-6, Caching, and SSE</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Sat, 10 Oct 2026 14:10:09 +0000</pubDate>
      <link>https://dev.to/yorchperaza/handling-frontier-model-churn-gpt-6-caching-and-sse-462o</link>
      <guid>https://dev.to/yorchperaza/handling-frontier-model-churn-gpt-6-caching-and-sse-462o</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;SSE Keep-Alives:&lt;/strong&gt; We shipped a model proxy fix (October 4) that injects application-level pings into Anthropic streams, preventing network timeouts during extended reasoning phases.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Cache Breakpoints:&lt;/strong&gt; The context kernel now strictly separates GPT-6 and GPT-5.6 system blocks (October 5) to preserve prefix-matching prompt cache breakpoints.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Azure Foundry GPT-6:&lt;/strong&gt; Full proxy support via Azure Foundry and the Responses API shipped October 5, ensuring cache and effort parity.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Visibility:&lt;/strong&gt; New dashboard updates (October 5-6) surface cache-write tokens and warn against routing inefficiencies when mixing frontier providers in Auto mode.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rapid frontier model releases create immediate pressure on developer tooling. On September 30, 2026, &lt;em&gt;thenextlevelsoftware.com&lt;/em&gt; reported an unprecedented wave of launches, including GPT-6, Claude Fable, Mythos 5.1, and Gemini 3.8 variants focused on tool use and execution. Days later, on October 4, &lt;em&gt;daily.dev&lt;/em&gt; detailed Microsoft's integration of GPT-6 Astra, Claude Opus 5.5, Sonnet 5.5, and DeepSeek 4.1 Flash across Azure Foundry and Copilot ecosystems.&lt;/p&gt;

&lt;p&gt;Integrating these models into an AI coding platform requires more than swapping an API URL. Frontier models with long-horizon execution and reasoning capabilities stress the underlying infrastructure. Network connections drop when models "think" for too long without emitting tokens, and prompt caching economics collapse if system instructions are not strictly versioned. &lt;/p&gt;

&lt;p&gt;Here is how the &lt;a href="https://monkeyscode.com" rel="noopener noreferrer"&gt;MonkeysCode&lt;/a&gt; engineering team handles these low-level mechanics to keep our editor, CLI, and Agent Manager running reliably.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SSE Timeout Problem During Reasoning
&lt;/h2&gt;

&lt;p&gt;Models that spend extended periods reasoning before returning a response introduce a distinct networking challenge. In standard Server-Sent Events (SSE) architectures, reverse proxies and load balancers typically enforce idle timeout thresholds. If a stream remains open but silent for an extended period (often 30 to 60 seconds depending on the cloud provider), the connection is forcibly closed by the infrastructure.&lt;/p&gt;

&lt;p&gt;When developers use models like Anthropic's thinking variants via the API, the model might compute extensively before yielding the first text token. In a standard setup, this results in premature connection closures, failed agent tasks, and a broken developer experience. The client sees a network error, and the agent loses its context window.&lt;/p&gt;

&lt;p&gt;On October 4, we shipped a fix to the MonkeysCode model proxy specifically to handle this lifecycle. The proxy now injects application-level keep-alive comments into the Anthropic SSE stream while the model is actively thinking.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;// Standard SSE payload with keep-alive injection
event: message
data: {"type": "thinking", "content": "Evaluating dependency tree..."}

: keep-alive-ping-1704382910

event: message
data: {"type": "thinking", "content": "Resolving circular imports..."}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;According to the SSE specification, any line beginning with a colon is treated as a comment and ignored by the client's &lt;code&gt;EventSource&lt;/code&gt; implementation. However, passing these bytes over the wire keeps the TCP connection saturated enough to bypass aggressive idle timeouts at the load balancer layer. This ensures that long-horizon reasoning tasks can complete without dropping the connection to the MonkeysCode desktop editor or CLI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserving Prompt Cache Breakpoints
&lt;/h2&gt;

&lt;p&gt;Prompt caching relies on strict prefix matching. For a KV cache hit to register on the provider's infrastructure, the sequence of tokens at the beginning of the prompt must exactly match a previously computed sequence. Even a single token difference in a system instruction will invalidate the cache for the entire subsequent payload.&lt;/p&gt;

&lt;p&gt;With the release of GPT-6, we faced a structural issue. If we appended GPT-6 specific instructions (such as new tool-use definitions or effort-level parameters) to a unified system block, we would alter the prefix for all users. This would immediately bust the cache for developers still running GPT-5.6, driving up latency and token costs.&lt;/p&gt;

&lt;p&gt;To solve this, we updated the MonkeysCode context kernel on October 5. The context kernel now strictly separates GPT-6 and GPT-5.6 system blocks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified representation of the context kernel routing&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildSystemBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseInstructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;generateGpt6SystemBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseInstructions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-5.6&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;generateGpt56SystemBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseInstructions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;generateDefaultSystemBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseInstructions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By maintaining separate, versioned system blocks, we ensure that prompt cache breakpoints remain intact. Developers using GPT-5.6 retain their high cache hit rates, while developers testing GPT-6 establish new, stable cache prefixes. This update shipped alongside our releases for CLI 1.0.2, Agent Manager 1.0.16, and editor 1.2.12.&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure Foundry GPT-6 Proxying and Visibility
&lt;/h2&gt;

&lt;p&gt;Supporting a new model requires aligning its specific API surface area with our existing multi-model infrastructure. On October 5, we shipped full model proxy support for GPT-6 via Azure Foundry and the Responses API. This integration ensures cache and effort parity, meaning developers can utilize GPT-6's adjustable effort levels without losing the caching benefits built into our platform.&lt;/p&gt;

&lt;p&gt;However, giving developers the ability to route between multiple frontier models introduces a new risk: cache fragmentation. MonkeysCode supports "Auto" model routing, allowing the platform to select the best model for a given task. If the system rapidly switches between an Anthropic model and an OpenAI model, the context window must be rebuilt from scratch each time, resulting in massive cache misses.&lt;/p&gt;

&lt;p&gt;To give developers visibility into this, we shipped two major updates to our dashboards:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Cache-Write Tokens (October 5):&lt;/strong&gt; The usage dashboards now explicitly surface cache-write tokens alongside standard input and output tokens. This allows developers to see exactly when they are paying to write to the KV cache versus when they are successfully reading from it.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Auto Tab Warnings (October 6):&lt;/strong&gt; The admin Auto tab on the Models &amp;amp; Pricing page now displays cache ratios. More importantly, it actively warns users when their routing configuration mixes frontier providers in a way that destroys cache continuity. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your Auto configuration routes a planning step to Claude Opus 5.5 and the execution step to GPT-6 Astra, the dashboard will flag the resulting cache miss penalty. Developers can then adjust their BYOK (Bring Your Own Key) or platform settings to maintain provider affinity during long sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Integrating frontier models is an exercise in infrastructure management. Rapid releases from Microsoft, Google, and Anthropic require tooling that can handle the low-level realities of HTTP streams, KV cache prefixes, and multi-provider routing economics.&lt;/p&gt;

&lt;p&gt;By injecting SSE keep-alives, separating system prompt blocks in the context kernel, and surfacing cache-write metrics, MonkeysCode ensures that developers can adopt new models like GPT-6 without sacrificing reliability or blowing through their context budgets. &lt;/p&gt;

&lt;p&gt;To explore these updates, check out the latest usage dashboards in your account, or read more about our multi-model approach in the &lt;a href="https://monkeyscode.com/docs" rel="noopener noreferrer"&gt;MonkeysCode documentation&lt;/a&gt;. If you are running agents locally, ensure you have updated to the latest &lt;a href="https://monkeyscode.com/download" rel="noopener noreferrer"&gt;CLI&lt;/a&gt; and Agent Manager versions.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>caching</category>
      <category>gpt6</category>
      <category>anthropic</category>
    </item>
    <item>
      <title>Multi-Model Routing and SSE Keep-Alives for Agent Resilience</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Fri, 09 Oct 2026 14:10:09 +0000</pubDate>
      <link>https://dev.to/yorchperaza/multi-model-routing-and-sse-keep-alives-for-agent-resilience-14fi</link>
      <guid>https://dev.to/yorchperaza/multi-model-routing-and-sse-keep-alives-for-agent-resilience-14fi</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Provider outages break workflows:&lt;/strong&gt; Recent Anthropic API disruptions (October 5-6, 2026) affecting Claude Opus 5.5, Mythos 5.1, and Fable 5.1 demonstrate the risk of single-provider reliance for long-running agent tasks.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Preventing silent timeouts:&lt;/strong&gt; MonkeysCode shipped a model-proxy fix on October 4 to keep Anthropic Server-Sent Event (SSE) streams alive during extended model "thinking" phases, bypassing aggressive load balancer idle timeouts.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Multi-model routing:&lt;/strong&gt; Workloads can route to Azure Foundry GPT-6, which was integrated on October 5 with cache and effort parity, ensuring tasks complete even when primary endpoints degrade.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Managing cache trade-offs:&lt;/strong&gt; The new Auto mode tab (shipped October 6) tracks cache ratios and warns on mixed frontier provider configurations to help developers balance resilience and API costs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building autonomous AI coding agents requires managing a significant amount of state. When an agent is tasked with planning a feature, editing multiple files across a codebase, and running tests, it relies on a continuous, reliable stream of reasoning from a frontier model.&lt;/p&gt;

&lt;p&gt;However, the underlying infrastructure powering these models is distributed, complex, and subject to disruption. On October 5 and 6, 2026, Anthropic experienced incidents causing elevated error rates across their Claude Opus 5.5, Mythos 5.1, and Fable 5.1 endpoints. According to reports from Statusgator and Claude.com, this disruption impacted Claude AI, the API, Claude Code, and Claude Cowork before recovering within twenty minutes.&lt;/p&gt;

&lt;p&gt;For developers relying on a single provider, an API outage or rate spike mid-workflow often results in lost state, hanging requests, or a complete halt to development tasks. When an agent is halfway through a complex refactor, a sudden 502 Bad Gateway or a silent timeout is more than an inconvenience; it is a loss of valuable context and compute time.&lt;/p&gt;

&lt;p&gt;To protect developer productivity, AI coding platforms must be engineered with network resilience and multi-model failovers as foundational requirements. Here is a technical look at how &lt;a href="https://monkeyscode.com" rel="noopener noreferrer"&gt;MonkeysCode&lt;/a&gt; handles connection management and multi-model routing to prevent agentic workflows from stalling during provider disruptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mechanics of SSE Keep-Alives During Model Thinking
&lt;/h2&gt;

&lt;p&gt;When you invoke &lt;a href="https://monkeyscode.com/capuchin" rel="noopener noreferrer"&gt;Capuchin&lt;/a&gt;, MonkeysCode's built-in coding agent, you are initiating a stateful, multi-step process. The agent reads the local file system, establishes context, and opens a streaming connection to an LLM provider using Server-Sent Events (SSE).&lt;/p&gt;

&lt;p&gt;SSE is a lightweight, unidirectional protocol built on top of HTTP, ideal for streaming text tokens from an LLM. However, during complex refactors, frontier models enter a "thinking" or reasoning phase. In this phase, the provider's API might not emit text tokens for several seconds or even minutes as it evaluates the prompt and computes the optimal output trajectory.&lt;/p&gt;

&lt;p&gt;At the network level, this silence presents a significant problem. Most standard reverse proxies, cloud load balancers, and client-side HTTP libraries enforce strict idle timeouts. If no bytes are transmitted over the TCP connection within that window (often 30 to 60 seconds), the intermediary drops the connection. The client receives a sudden &lt;code&gt;ECONNRESET&lt;/code&gt; or a silent hang, and the agent's context window is lost.&lt;/p&gt;

&lt;p&gt;To mitigate this, MonkeysCode shipped a model-proxy fix on October 4, 2026, specifically designed to keep Anthropic SSE streams alive while the model thinks. By injecting periodic, non-disruptive keep-alive pings (typically formatted as SSE comments like &lt;code&gt;: keep-alive\n\n&lt;/code&gt;) into the stream at the proxy layer, we ensure the connection remains active through the load balancer's idle timeout window. This prevents silent drops during long-running planning phases, ensuring the agent can successfully transition from thinking to emitting actionable code edits without the connection being severed by overzealous network infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Model Routing: Bypassing Endpoint Degradation
&lt;/h2&gt;

&lt;p&gt;Even with robust connection management, a complete endpoint outage requires a different strategy. When Anthropic's Opus 5.5 endpoints experienced elevated error rates on October 6, developers locked into a single provider were left waiting for resolution.&lt;/p&gt;

&lt;p&gt;Resilience requires redundancy. MonkeysCode supports multi-model configurations, allowing developers to route requests across different frontier engines, local models, or through a Bring Your Own Key (BYOK) setup.&lt;/p&gt;

&lt;p&gt;On October 5, 2026, we expanded this redundancy by integrating GPT-6 via Azure Foundry, complete with cache and effort parity alongside our existing Anthropic support. By maintaining parity in how context is cached and how reasoning effort is parameterized, developers can switch models without rewriting their system prompts or losing performance baselines.&lt;/p&gt;

&lt;p&gt;If a primary provider experiences a spike in 5xx errors, developers can immediately route the agent's next step to a secondary provider. Because the &lt;a href="https://monkeyscode.com/features" rel="noopener noreferrer"&gt;MonkeysCode editor&lt;/a&gt; and Agent Manager maintain the local state and file context, the transition happens cleanly. The new model reads the existing context and continues the task.&lt;/p&gt;

&lt;p&gt;Furthermore, to optimize this transition, we shipped an update to our context-kernel on October 5 that keeps GPT-6 and 5.6 system blocks separate for cache breakpoints. This architectural decision ensures that when you do route to a different model, the system prompt structure aligns with that specific provider's optimal caching strategy, reducing unnecessary token processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing Cache Trade-Offs in Auto Mode
&lt;/h2&gt;

&lt;p&gt;Switching providers mid-task introduces a specific trade-off: prompt caching. Frontier models rely heavily on prompt caching to reduce latency and API costs when processing large codebases. When you switch from Anthropic to Azure Foundry, the new provider does not have your codebase in its cache. The first request to the new provider will incur a full context-window read, increasing latency and cost for that specific turn.&lt;/p&gt;

&lt;p&gt;To make these trade-offs visible, MonkeysCode introduced the Auto mode tab on October 6, 2026. This interface provides direct visibility into your cache ratios. Crucially, it warns developers when they configure mixed frontier providers in Auto mode.&lt;/p&gt;

&lt;p&gt;If you configure Auto mode to round-robin or fallback between Claude Opus 5.5 and GPT-6, you risk thrashing the cache on both providers. The Auto tab surfaces these metrics, showing exactly how much of your context is hitting the cache versus requiring a fresh read. We also updated our usage dashboards on October 5 to explicitly show cache-write tokens.&lt;/p&gt;

&lt;p&gt;This visibility allows senior engineers to make informed decisions: accept the cache-miss penalty for the sake of immediate failover during an outage, or pin to a single provider and wait for the disruption to resolve to maximize cache utilization. The platform does not force a specific path; it provides the telemetry and the routing controls necessary to adapt to real-world infrastructure conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Agentic coding workflows are only as reliable as the infrastructure executing them. By implementing strict SSE keep-alives to survive long thinking phases, and providing transparent multi-model routing with cache parity, MonkeysCode ensures your development environment remains functional even when frontier providers degrade.&lt;/p&gt;

&lt;p&gt;You can review your caching metrics and configure your model routing today in the &lt;a href="https://monkeyscode.com/download" rel="noopener noreferrer"&gt;MonkeysCode editor&lt;/a&gt; or via the &lt;a href="https://monkeyscode.com/code-agent" rel="noopener noreferrer"&gt;Agent Manager&lt;/a&gt;. For teams looking to standardize these resilient workflows across their organization, explore our &lt;a href="https://monkeyscode.com/for-teams" rel="noopener noreferrer"&gt;team plans&lt;/a&gt; and &lt;a href="https://monkeyscode.com/docs" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>reliability</category>
      <category>multimodel</category>
      <category>anthropic</category>
    </item>
    <item>
      <title>MonkeysCode Ships Auto Mode: Phase-Based Model Routing</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Fri, 09 Oct 2026 04:10:11 +0000</pubDate>
      <link>https://dev.to/yorchperaza/monkeyscode-ships-auto-mode-phase-based-model-routing-2eb3</link>
      <guid>https://dev.to/yorchperaza/monkeyscode-ships-auto-mode-phase-based-model-routing-2eb3</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MonkeysCode Auto mode is now live across Editor 1.3.1, CLI 1.1.1, and Agent Manager 1.1.1.&lt;/li&gt;
&lt;li&gt;The router dynamically shifts models across distinct agent phases (&lt;code&gt;plan&lt;/code&gt; → &lt;code&gt;execute&lt;/code&gt; → &lt;code&gt;review&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Context handoff briefs bridge state between frontier planning models and standard execution models without polluting context.&lt;/li&gt;
&lt;li&gt;Pricing is calculated strictly per-turn based on the routed model, avoiding opaque flat premiums.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Developers using AI coding agents face a consistent balancing act between cost and capability. Routing all requests to a frontier model provides high reasoning capacity for architecture and multi-file planning, but burns through budget on routine execution tasks like boilerplate generation or minor syntax edits. Conversely, pinning an agent to a cheaper, smaller model saves money but routinely fails when the agent needs to plan complex state changes across a large repository.&lt;/p&gt;

&lt;p&gt;To address this, MonkeysCode has shipped Auto mode across the &lt;a href="https://monkeyscode.com/download" rel="noopener noreferrer"&gt;MonkeysCode Editor&lt;/a&gt;, CLI, and Agent Manager. Auto mode replaces static model selection with a dynamic router that shifts models based on the current phase of the agent's workflow. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Problem: Cost vs. Capability
&lt;/h2&gt;

&lt;p&gt;Before Auto mode, developers had to manually toggle between models depending on the task at hand. If you needed &lt;a href="https://monkeyscode.com/capuchin" rel="noopener noreferrer"&gt;Capuchin&lt;/a&gt;, our built-in coding agent, to scaffold a new microservice, you would manually select a frontier model. If you just needed it to write unit tests for an existing file, you might switch to a faster, cheaper model.&lt;/p&gt;

&lt;p&gt;This manual switching creates friction. Developers often forget to switch back, leading to either failed complex tasks (because a small model was left active) or inflated bills (because a frontier model was used for hours of simple refactoring). &lt;/p&gt;

&lt;p&gt;Auto mode removes this friction by making the routing decision programmatically based on the active phase of the agent's lifecycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase-Based Routing Architecture
&lt;/h2&gt;

&lt;p&gt;Auto mode operates by breaking down a developer's prompt into a strict lifecycle: &lt;code&gt;plan&lt;/code&gt; → &lt;code&gt;execute&lt;/code&gt; → &lt;code&gt;review&lt;/code&gt;. Rather than handling this entirely within a single context window of a single model, the MonkeysCode &lt;code&gt;model-proxy&lt;/code&gt; and &lt;code&gt;context-kernel&lt;/code&gt; route the work dynamically.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Plan:&lt;/strong&gt; The initial request requires deep reasoning. The router assigns this to a frontier model. The frontier model analyzes the codebase, determines the necessary file changes, and constructs a detailed execution plan. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute:&lt;/strong&gt; Once the plan is approved, the task shifts to the execute phase. The router escalates the execution steps to a faster, more cost-effective model suited for writing the actual code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review:&lt;/strong&gt; After execution, a review phase validates the changes, ensuring the implementation matches the initial architecture plan.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This role-based routing was engineered over several distinct build steps. In build step 7, we formalized the roles inside Auto (&lt;code&gt;plan&lt;/code&gt; → &lt;code&gt;execute&lt;/code&gt; → &lt;code&gt;review&lt;/code&gt;). In build step 8, we integrated the SDK plan error and route events, and added Auto mode UI elements like the plan card and routing chips to the Agent Manager. Furthermore, as documented in our D14 and D15 specifications, every app opens on Capuchin by default, and any subsequent fix round is treated as a new task, ensuring the router re-evaluates the necessary model for the fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context Handoffs
&lt;/h2&gt;

&lt;p&gt;A major technical hurdle in multi-model routing is maintaining state. Passing the entire planning dialogue into an execution model is inefficient. It wastes context window space, increases latency, and risks confusing smaller models with excessive architectural deliberation. &lt;/p&gt;

&lt;p&gt;To solve this, we introduced the handoff brief in build step 5. The &lt;code&gt;model-proxy&lt;/code&gt; generates a concise handoff brief that distills the architectural decisions and exact file targets from the &lt;code&gt;plan&lt;/code&gt; phase. This brief is passed to the execution model, providing a clean, highly focused state without the overhead of the entire conversational history. The context kernel also provides phase and signal hints directly to the models to ensure they understand their current role in the pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transparent Per-Turn Pricing
&lt;/h2&gt;

&lt;p&gt;A common industry practice for "auto" or "premium" modes is to charge a flat, inflated rate regardless of which model actually processes the request. We rejected this approach. &lt;/p&gt;

&lt;p&gt;With the release of Editor 1.3.1 and CLI 1.1.1, we explicitly dropped the "Auto frontier" button and removed the &lt;code&gt;/frontier&lt;/code&gt; command. Instead, the &lt;code&gt;context-kernel&lt;/code&gt; prices Auto turns strictly as the routed model. If the router assigns an execution task to a cheaper model, you are billed the exact rate for that cheaper model. &lt;/p&gt;

&lt;p&gt;To make this transparent, we shipped a dedicated Auto usage card and route log in the web dashboard (build step 9), accessible via the &lt;code&gt;GET /v1/usage/auto-summary&lt;/code&gt; endpoint. The web dashboard now features a dedicated Auto mode page, a home section highlighting active routes, and detailed billing highlights. You can view your exact per-model breakdown at &lt;a href="https://monkeyscode.com/pricing" rel="noopener noreferrer"&gt;monkeyscode.com/pricing&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Auto Mode
&lt;/h2&gt;

&lt;p&gt;Auto mode is now the default experience across the MonkeysCode ecosystem. &lt;/p&gt;

&lt;h3&gt;
  
  
  In the CLI
&lt;/h3&gt;

&lt;p&gt;We have updated the MonkeysCode CLI to version 1.1.1. The &lt;code&gt;/model&lt;/code&gt; command now prominently displays Auto. We also shipped CLI tabs, mouse and command menus, and an Auto usage card directly in the terminal interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Update to the latest CLI&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; monkeyscode-cli

&lt;span class="c"&gt;# Start an agent session in Auto mode&lt;/span&gt;
monkeyscode run &lt;span class="nt"&gt;--mode&lt;/span&gt; auto
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to the CLI updates, build step 9 introduced Agent Manager SSH support, model-proxy identity verification, and platform CORS updates to support distributed agent deployments.&lt;/p&gt;

&lt;h3&gt;
  
  
  In the Editor and Agent Manager
&lt;/h3&gt;

&lt;p&gt;In Editor 1.3.1, we introduced the Auto picker, route chips, and plan cards. We quickly followed up with Editor 1.3.2 and Agent Manager 1.1.2, which introduced plan approval under Auto. &lt;/p&gt;

&lt;p&gt;This means that even when Auto mode is handling the routing, you retain control. The frontier model generates the plan, and the Editor presents a plan approval UI. Only after you approve the plan does the &lt;code&gt;model-proxy&lt;/code&gt; generate the handoff brief and route the task to the execution model. We also added an Auto status strip and a per-tab change tray in 1.3.2 to make the active phase and model completely visible.&lt;/p&gt;

&lt;p&gt;For teams managing internal models, Auto mode respects the admin profile. The versioned Auto mode model config and admin API (shipped in build step 4) ensure that Auto only routes to servable models approved by your organization. The platform also keeps the Auto config partial index intact across &lt;code&gt;schema:update&lt;/code&gt; operations, ensuring zero downtime for your routing rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Auto mode is not magic; it is pragmatic resource allocation. By formally separating the agent lifecycle into &lt;code&gt;plan&lt;/code&gt;, &lt;code&gt;execute&lt;/code&gt;, and &lt;code&gt;review&lt;/code&gt; phases, and utilizing context handoff briefs, MonkeysCode allows developers to leverage the reasoning of frontier models without paying frontier prices for basic syntax edits. &lt;/p&gt;

&lt;p&gt;Update to Editor 1.3.2 and CLI 1.1.1 today to start using Auto mode. If you are new to the platform, we recently shipped a permanent 14-day promo trial (replacing the old referral system)—check out the &lt;a href="https://monkeyscode.com/docs" rel="noopener noreferrer"&gt;MonkeysCode documentation&lt;/a&gt; to get started.&lt;/p&gt;

</description>
      <category>automode</category>
      <category>releasenotes</category>
      <category>routing</category>
      <category>cli</category>
    </item>
    <item>
      <title>Countering Developer Burnout with Agentic Test Execution</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Thu, 08 Oct 2026 14:10:08 +0000</pubDate>
      <link>https://dev.to/yorchperaza/countering-developer-burnout-with-agentic-test-execution-4kfn</link>
      <guid>https://dev.to/yorchperaza/countering-developer-burnout-with-agentic-test-execution-4kfn</guid>
      <description>&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The QA Fatigue Epidemic:&lt;/strong&gt; Recent surveys reveal developers feel reduced to "human meat proxies," burning out from manually debugging "almost right" AI code.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Junior Deskilling:&lt;/strong&gt; Overreliance on isolated AI snippets is impairing junior engineers' ability to independently debug and understand software architecture.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Automated Verification:&lt;/strong&gt; MonkeysCode Editor 1.2.12 and CLI 1.0.2 shift verification from manual reading to automated test-and-repair loops.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Agentic Workflows:&lt;/strong&gt; Capuchin executes test suites and iterates across multiple files autonomously, ensuring code is validated before it reaches your review queue.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The "Almost Right" Illusion and Developer Burnout
&lt;/h2&gt;

&lt;p&gt;The narrative around AI coding tools has largely focused on generation speed, but the reality of maintaining that code is catching up with the industry. On October 6, 2026, Business Insider reported on a Stack Overflow survey revealing a stark reality: engineers are facing an identity crisis. Many feel reduced to "button-pushers and human meat proxies."&lt;/p&gt;

&lt;p&gt;Instead of architecting software, developers are spending their days acting as manual QA testers for plausible-sounding but subtly broken AI snippets. This sentiment is not isolated. Reports from TechTarget on October 5 and Medium on September 30 highlight the exact same exhaustion. Engineers are experiencing severe QA fatigue because current AI coding workflows often output code that is "almost right."&lt;/p&gt;

&lt;p&gt;Reviewing code that looks correct at first glance but contains subtle logic errors requires a significantly higher cognitive load than writing it from scratch. When a human writes code, they build a mental model of the system. When a human reviews a peer's pull request, they can ask the author for their context. When a developer reviews an AI snippet, they are forced to reverse-engineer the intent of a statistical model, hunting for hallucinated variables, type mismatches, and edge-case failures. &lt;/p&gt;

&lt;p&gt;When you paste a snippet into a file, the AI's job ends, and your job begins. You have to wire it up, write the tests, run the tests, find the errors, correct the implementation, and rerun the test. This manual verification loop—acting as the runtime environment for a black-box model—is the root cause of the burnout developers are vocalizing today.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Deskilling of Junior Engineers
&lt;/h2&gt;

&lt;p&gt;The impact of this dynamic extends beyond senior developer burnout; it is actively affecting the next generation of engineers. On October 5, 2026, LeadDev reported that overreliance on AI generation tools is impairing junior engineers' ability to independently debug and understand complex software.&lt;/p&gt;

&lt;p&gt;Struggling through a stack trace, navigating a codebase to find a failing test, and manually tracing data flow are the crucibles where architectural understanding is forged. When junior developers rely on AI to generate code blocks without engaging in the debugging process, their system-level comprehension atrophies. They become highly dependent on the tool to fix the very errors the tool created, leading to a shallow understanding of the underlying architecture. If the industry continues to treat developers as passive reviewers of AI output, we risk creating a generation of engineers who can generate boilerplate but cannot debug a production outage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shifting from Manual QA to Agentic Verification
&lt;/h2&gt;

&lt;p&gt;To counter this burnout and deskilling, the workflow must change. The burden of verifying AI-generated code must shift from human line-by-line reading to automated execution. &lt;a href="https://monkeyscode.com" rel="noopener noreferrer"&gt;MonkeysCode&lt;/a&gt; addresses passive "button-pushing" code generation through &lt;a href="https://monkeyscode.com/capuchin" rel="noopener noreferrer"&gt;Capuchin&lt;/a&gt; in the desktop IDE and the Agent Manager CLI.&lt;/p&gt;

&lt;p&gt;With the release of MonkeysCode Editor 1.2.12 and Agent Manager 1.0.16 on October 5, 2026, we have solidified a workflow that prioritizes verification over raw generation. Capuchin does not just dump code into your active file. It plans, edits multiple files, runs tests, and iterates based on the output.&lt;/p&gt;

&lt;p&gt;When you assign a task to Capuchin, it writes the implementation and then executes the associated test suite. If the test fails, Capuchin reads the stack trace, analyzes the failure, and attempts to repair the code. This autonomous test-and-repair loop continues until the tests pass or the agent reaches its iteration limit. By the time a human reviews the diff, the code has already been validated against the project's actual test suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supervised Runs with Agent Manager
&lt;/h2&gt;

&lt;p&gt;For developers who prefer terminal-driven workflows or need to integrate agentic tasks into broader scripts, we shipped MonkeysCode CLI 1.0.2 alongside Agent Manager 1.0.16. This allows for supervised agent runs directly from your command line.&lt;/p&gt;

&lt;p&gt;Instead of manually copying and pasting snippets, you can instruct the CLI to handle the implementation and verification loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Running a supervised agent task via MonkeysCode CLI 1.0.2&lt;/span&gt;
npx @monkeyscode/cli@1.0.2 task &lt;span class="s2"&gt;"Implement the user authentication middleware and ensure all tests in auth.test.ts pass"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Agent Manager orchestrates the process, providing clear visibility into the agent's actions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Agent] Planning changes across 2 files: middleware/auth.ts, tests/auth.test.ts
[Agent] Applying edits to middleware/auth.ts
[Agent] Executing test suite: npm run test -- tests/auth.test.ts
[System] Test failed: Expected 401 but received 500.
[Agent] Analyzing stack trace...
[Agent] Applying fix to middleware/auth.ts
[Agent] Executing test suite: npm run test -- tests/auth.test.ts
[System] 4 passing (850ms)
[Agent] Task complete. Ready for review.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the agent completes its run, the human review process is highly targeted. In Editor 1.2.11, we shipped improved diff navigation: diff card headers directly open the affected file tab for targeted inspection. You aren't hunting for where the AI made changes; the editor guides you directly to the modified files, allowing you to review the logic with the confidence that the syntax and tests are already passing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model Choice and BYOK
&lt;/h2&gt;

&lt;p&gt;A significant part of developer frustration stems from being locked into opaque systems that offer no control over the underlying reasoning engine. MonkeysCode addresses this by supporting model choice via multi-model routing and BYOK (Bring Your Own Key).&lt;/p&gt;

&lt;p&gt;By allowing teams to route requests to the models best suited for their specific codebase and providing the flexibility to bring their own API keys, developers regain control over their tooling. This ensures that the agentic workflows are powered by the models you trust, rather than forcing you into a one-size-fits-all black box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;The era of developers acting as full-time QA testers for AI snippets is unsustainable. The data from Stack Overflow, TechTarget, and LeadDev clearly shows that this dynamic leads to burnout and deskilling. By adopting agentic workflows that execute tests and iterate autonomously across multiple files, teams can shift the burden of verification back to the machine. Tools like Capuchin and the MonkeysCode CLI are designed to restore the developer's role as an architect, leaving the tedious work of syntax checking and test-fixing to the agent.&lt;/p&gt;

&lt;p&gt;Explore how automated verification can improve your workflow by downloading the &lt;a href="https://monkeyscode.com/download" rel="noopener noreferrer"&gt;MonkeysCode Editor&lt;/a&gt; or reading our &lt;a href="https://monkeyscode.com/docs" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;. For organizational deployment, review our &lt;a href="https://monkeyscode.com/for-teams" rel="noopener noreferrer"&gt;team plans&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>devrel</category>
      <category>aicoding</category>
      <category>capuchin</category>
      <category>automation</category>
    </item>
    <item>
      <title>Hardening Prompt Caches and SSE Streams for Coding Agents</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Wed, 07 Oct 2026 14:10:10 +0000</pubDate>
      <link>https://dev.to/yorchperaza/hardening-prompt-caches-and-sse-streams-for-coding-agents-315g</link>
      <guid>https://dev.to/yorchperaza/hardening-prompt-caches-and-sse-streams-for-coding-agents-315g</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Anthropic SSE Keep-Alives:&lt;/strong&gt; We updated the model proxy to inject keep-alive pings during extended Anthropic "thinking" phases, preventing load balancer and NAT gateway timeouts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPT-6 Cache Breakpoints:&lt;/strong&gt; The context kernel now strictly separates GPT-6 and GPT-5.6 system blocks to ensure deterministic prefix matching and reliable cache breakpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure Foundry Support:&lt;/strong&gt; GPT-6 is now supported via Azure Foundry and the Responses API with full cache and effort parity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache Observability:&lt;/strong&gt; Usage dashboards now surface cache-write tokens, and the Auto mode tab warns against provider-mix cache invalidation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;New Releases:&lt;/strong&gt; MonkeysCode Editor 1.2.12, Agent Manager 1.0.16, and CLI 1.0.2 are now available.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;When deploying autonomous coding agents for multi-turn tasks, infrastructure reliability is just as critical as the reasoning capabilities of the underlying models. During extended sessions, where an agent like Capuchin plans, edits multiple files, runs tests, and iterates, developers face two primary failure modes: dropped connections during long reasoning phases and cache misses across model switches.&lt;/p&gt;

&lt;p&gt;These failures are not just minor inconveniences. A dropped connection forces the agent to retry, duplicating work and increasing latency. A cache miss forces the model to recompute the KV cache for the entire context window, leading directly to bloated API bills and degraded time-to-first-token (TTFT). &lt;/p&gt;

&lt;p&gt;Over the last two weeks, we shipped a series of updates to the MonkeysCode context kernel and model proxy designed to harden prompt caching and agent stream reliability across frontier models.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mechanics of SSE Timeouts During Extended Reasoning
&lt;/h2&gt;

&lt;p&gt;Modern frontier models utilize extended reasoning or "thinking" phases before emitting the first token of a response. While this drastically improves the quality of complex code generation, it introduces a significant networking challenge when streaming responses via Server-Sent Events (SSE).&lt;/p&gt;

&lt;p&gt;When a client requests a completion, the proxy establishes an HTTP connection with chunked transfer encoding. In a standard generation scenario, the model emits tokens rapidly, ensuring continuous byte flow over the TCP connection. However, during a long thinking phase, the model may not emit any standard completion tokens for an extended period. &lt;/p&gt;

&lt;p&gt;Network intermediaries—such as NAT gateways, load balancers, and reverse proxies—are configured with idle timeout thresholds. If no data traverses the connection before the timeout is reached, the intermediary assumes the connection is dead and unceremoniously severs it. For the developer, this manifests as a broken stream and an agent that halts midway through a complex refactor.&lt;/p&gt;

&lt;p&gt;To address this, we shipped a fix to the MonkeysCode model proxy: &lt;code&gt;fix(model-proxy): keep Anthropic SSE streams alive while the model thinks&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;At the proxy layer, we now monitor the idle time between chunks during Anthropic streams. If the model is in a thinking state and the time since the last byte approaches the threshold, the proxy injects a standard SSE comment payload (e.g., &lt;code&gt;: ping\n\n&lt;/code&gt;). Because SSE specifications require clients to ignore lines starting with a colon, these pings safely traverse the network, resetting intermediary idle timers without polluting the actual completion stream parsed by the Agent Manager. This ensures the connection remains open regardless of how long the model spends planning its next edit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deterministic Cache Breakpoints for GPT-6
&lt;/h2&gt;

&lt;p&gt;Prompt caching is mechanism that allows providers to reuse the KV cache of previously processed tokens, provided the token sequence matches exactly from the beginning of the prompt (the prefix). Even a single differing token early in the system prompt will invalidate the prefix, causing a total cache miss for the remainder of the context window.&lt;/p&gt;

&lt;p&gt;With the introduction of GPT-6, we observed that sharing dynamic system blocks between model generations was leading to unpredictable cache behavior. GPT-6 and GPT-5.6 often require slightly different system instructions to optimize their respective coding capabilities. If these instructions are concatenated or conditionally injected into the same block, the prefix diverges early, destroying cache efficiency.&lt;/p&gt;

&lt;p&gt;To enforce predictable caching, we shipped an update to the context kernel: &lt;code&gt;feat(context-kernel): GPT-6 model refresh; keep GPT-6/5.6 system blocks separate for cache breakpoints&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;By physically separating the system blocks in the context kernel, we ensure that the shared foundational context—such as project architecture, file structures, and core rules—remains identical up to the designated breakpoint. The model-specific instructions are appended only after the shared prefix, maximizing the number of tokens that can be safely retrieved from the cache. &lt;/p&gt;

&lt;p&gt;Additionally, we have fully integrated GPT-6 via Azure Foundry and the Responses API (&lt;code&gt;feat(model-proxy): GPT-6 via Azure Foundry and the Responses API, with cache and effort parity&lt;/code&gt;), ensuring that enterprise developers using BYOK (Bring Your Own Key) on Azure receive the exact same cache and effort parity as those using the direct OpenAI API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability and Cache Ratio Tracking in Auto Mode
&lt;/h2&gt;

&lt;p&gt;MonkeysCode's "Auto" model routing allows the platform to dynamically select the best model for a given sub-task. While powerful, mixing models from different providers (e.g., routing a planning task to an Anthropic model and a syntax-checking task to an OpenAI model) introduces a hidden cost: cross-provider cache invalidation.&lt;/p&gt;

&lt;p&gt;Anthropic and OpenAI use completely different tokenizers and isolated infrastructure. A context window cached on Anthropic's servers is entirely useless to an OpenAI model. If Auto mode rapidly alternates between providers in a multi-turn Capuchin run, the agent will incur the cost of processing the full context window on every single turn.&lt;/p&gt;

&lt;p&gt;Developers need visibility into these mechanics to optimize their configurations. Relying on end-of-month billing reports is too slow. &lt;/p&gt;

&lt;p&gt;We recently rolled out several updates to the admin and usage dashboards to make these mechanics transparent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Cache-Write Tokens:&lt;/strong&gt; The usage dashboards now explicitly track and display cache-write tokens (&lt;code&gt;feat(web): usage dashboards show cache-write tokens&lt;/code&gt;). Developers can immediately see when their agents are writing to the cache versus reading from it.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Auto Mode Cache Ratios:&lt;/strong&gt; In the Models &amp;amp; Pricing settings (&lt;code&gt;feat(admin): Auto mode tab on Models &amp;amp;amp; Pricing (Auto build step 4)&lt;/code&gt;), the new Auto tab surfaces real-time cache ratios. &lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Provider-Mix Warnings:&lt;/strong&gt; The Auto tab now actively warns administrators when their model routing configuration mixes frontier providers in a way that will degrade cache efficiency (&lt;code&gt;feat(admin): Auto tab shows cache ratios and warns on mixed frontier providers&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These additions allow senior engineers to make informed trade-offs. If a specific task requires the unique reasoning capabilities of two different providers, the developer can accept the cache miss. But they will no longer do so blindly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Latest Releases and Next Steps
&lt;/h2&gt;

&lt;p&gt;These infrastructure improvements require the latest client software to function correctly, particularly the updates to how the Agent Manager parses SSE streams and how the editor constructs context windows.&lt;/p&gt;

&lt;p&gt;We have released MonkeysCode Editor 1.2.12, Agent Manager 1.0.16, and CLI 1.0.2. We recommend all teams upgrade to these versions immediately to benefit from the reduced latency and improved stream reliability.&lt;/p&gt;

&lt;p&gt;To update the CLI, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @monkeyscode/cli@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The desktop IDE will prompt you to update to 1.2.12 on your next launch, or you can download it directly from &lt;a href="https://monkeyscode.com/download" rel="noopener noreferrer"&gt;monkeyscode.com/download&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For teams participating in upcoming build events, we have also published new hackathon documentation (&lt;code&gt;docs: hackathon docs&lt;/code&gt;), which details how to optimize agent context windows for rapid prototyping. You can find these guides at &lt;a href="https://monkeyscode.com/docs" rel="noopener noreferrer"&gt;monkeyscode.com/docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By managing state cleanly across the proxy and kernel layers, we ensure that Capuchin spends less time re-reading unchanged files and recovering from network drops, and more time writing working code.&lt;/p&gt;

</description>
      <category>promptcaching</category>
      <category>agentreliability</category>
      <category>gpt6</category>
      <category>anthropic</category>
    </item>
    <item>
      <title>Stop Prompting, Start Pairing: How to Actually Get Better at Code by Working With AI Agents</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Wed, 16 Sep 2026 23:45:44 +0000</pubDate>
      <link>https://dev.to/yorchperaza/stop-prompting-start-pairing-how-to-actually-get-better-at-code-by-working-with-ai-agents-57ld</link>
      <guid>https://dev.to/yorchperaza/stop-prompting-start-pairing-how-to-actually-get-better-at-code-by-working-with-ai-agents-57ld</guid>
      <description>&lt;p&gt;I've been writing code for more than 20 years: PHP, Drupal, TypeScript, cloud infrastructure, and now LLM serving for an agentic IDE I'm building. Over the last year of working with coding agents every day, I've seen developers split into two groups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Developers who got sharper.&lt;/strong&gt; They understand more languages, make better architecture decisions, and review code faster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developers who got rusty.&lt;/strong&gt; They ship more code than ever, but they couldn't explain half of it without the agent open.
The tool isn't the difference. Both groups use the same agents. The difference is &lt;strong&gt;what they delegate&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The trap: delegating the thinking
&lt;/h2&gt;

&lt;p&gt;The default loop looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prompt → accept → run → "works" → ship
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It feels productive, and in the short term it is. But this loop trains you in only one thing: writing prompts. You get output without gaining skill. Six months later, you're faster at producing code and slower at understanding it.&lt;/p&gt;

&lt;p&gt;The fix isn't to stop using agents. It's to change the relationship. Treat the agent as three things at once:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A senior pair programmer&lt;/strong&gt; who challenges your decisions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A rubber duck that talks back&lt;/strong&gt; when you need to think out loud&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A student&lt;/strong&gt; you have to review and correct
Here are the practices that made me better, not just faster.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Design first, and make the agent argue with you
&lt;/h2&gt;

&lt;p&gt;Before any code, ask for options instead of an implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I need to add multi-tenant support to this service.
Don't write code yet. Give me 3 architectural approaches
(e.g., shared schema, schema-per-tenant, database-per-tenant),
with trade-offs in cost, isolation, migrations, and operational
complexity. Then ask me which one I'd pick and why.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;strong&gt;you&lt;/strong&gt; choose, and you write down why. Now you're practicing the most valuable skill in the job, making architecture decisions under trade-offs, with an expert opponent available 24/7.&lt;/p&gt;

&lt;p&gt;Bonus: save that reasoning as an ADR (Architecture Decision Record). Future you, and future agents, will thank you.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. "Attempt first" for anything you want to learn
&lt;/h2&gt;

&lt;p&gt;For code you just need done, let the agent write it. For code in an area you want to master, write it yourself first, then ask for a review:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here's my implementation. Review it like a strict senior engineer.
Point out bugs, non-idiomatic code, performance issues, and
edge cases I missed. Don't rewrite it — explain each issue
and let me fix it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The line "don't rewrite it" matters. If the agent fixes the issue, you learn nothing. If you fix it, you remember it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The "explain it back" rule
&lt;/h2&gt;

&lt;p&gt;My rule before merging any agent-generated code: &lt;strong&gt;I must be able to explain every line.&lt;/strong&gt; If I can't, that's not a reason to trust it. It's a gap in what I know.&lt;/p&gt;

&lt;p&gt;When I hit a line I don't fully understand, I flip the roles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Don't explain this code to me. Quiz me on it.
Ask me 5 questions about what this function does,
why it's written this way, and what breaks if we change it.
Tell me which answers are wrong.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Being quizzed exposes gaps much faster than reading an explanation.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Learn new languages by porting code you already know
&lt;/h2&gt;

&lt;p&gt;The fastest way I've found to learn a new language with an agent is to &lt;strong&gt;port something you already understand deeply&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Take a small module you wrote in your main language (for me, PHP) and rewrite it yourself in the target language (Go, Rust, whatever). Then ask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I ported this PHP class to Go. Compare both versions.
Where is my Go code "PHP written in Go syntax"?
Show me the idiomatic Go approach for each case and explain
the language concept behind it (interfaces, error handling,
ownership, concurrency, etc.).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because you already know the domain logic, all of your attention goes to the language itself: its idioms, its type system, its way of handling errors. You learn the language from your own mistakes instead of tutorial examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Always ask for the "why" and a source you can verify
&lt;/h2&gt;

&lt;p&gt;Agents are confident even when they're wrong. Turn that into a learning habit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Why is this the correct approach? Point me to the relevant
documentation section, RFC, or language spec so I can verify it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;strong&gt;actually open the source&lt;/strong&gt;. Half the time you'll learn something the agent didn't mention. The other half, you'll catch it being wrong, and that teaches you even more.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Break things on purpose
&lt;/h2&gt;

&lt;p&gt;This is my favorite one. Use the agent to create debugging exercises from your own codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Introduce one subtle bug into this module — something that
passes a quick review but fails in production (race condition,
off-by-one, timezone, null handling). Don't tell me where.
Give me only the symptom a user would report.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then hunt it down without help. It's like a debugging gym built from your own real code. You can also have the agent write failing tests and make them pass yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Architecture katas: let the agent be the chaotic stakeholder
&lt;/h2&gt;

&lt;p&gt;Designs look great until the requirements change. So simulate the change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here's my design for an order system. Act as a product manager
who changes requirements every round. Give me one new requirement
at a time (multi-currency, partial refunds, 10x traffic,
GDPR deletion...). After each one, ask me what changes in my design.
Tell me when my design starts to break.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After five rounds, you'll know where your design is fragile, and why patterns like event sourcing, CQRS, or hexagonal architecture exist. You'll understand them from experience, not from a blog post.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Review the agent like you'd review a junior, and keep a log
&lt;/h2&gt;

&lt;p&gt;Treat every agent PR as a pull request from a talented junior developer: read it, question it, request changes.&lt;/p&gt;

&lt;p&gt;Then keep a simple &lt;strong&gt;"agent mistakes log"&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Mistake&lt;/th&gt;
&lt;th&gt;Why it was wrong&lt;/th&gt;
&lt;th&gt;Pattern to remember&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;09/12&lt;/td&gt;
&lt;td&gt;N+1 queries in listing&lt;/td&gt;
&lt;td&gt;Lazy loading inside loop&lt;/td&gt;
&lt;td&gt;Eager-load relations in list views&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;09/14&lt;/td&gt;
&lt;td&gt;Swallowed exception&lt;/td&gt;
&lt;td&gt;Empty catch block&lt;/td&gt;
&lt;td&gt;Never catch without logging or rethrowing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After a month, this log becomes your personal list of anti-patterns. The agent's mistakes turn into your expertise.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Write your standards down, because it forces you to know them
&lt;/h2&gt;

&lt;p&gt;Most agent tools support a project rules file (for example &lt;code&gt;AGENTS.md&lt;/code&gt;, &lt;code&gt;CLAUDE.md&lt;/code&gt;, or similar) where you describe conventions, architecture, and boundaries.&lt;/p&gt;

&lt;p&gt;Writing that file is a surprisingly hard exercise. You have to spell out things you've only ever "felt":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why is the domain layer separate from the infrastructure layer?&lt;/li&gt;
&lt;li&gt;When do we use a queue versus a synchronous call?&lt;/li&gt;
&lt;li&gt;What does "done" mean in this codebase?
If you can't write your architecture down clearly, you don't understand it clearly yet. The agent just gave you a reason to find out.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A simple weekly routine
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Daily:&lt;/strong&gt; Apply the "explain it back" rule to every merged PR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Twice a week:&lt;/strong&gt; Write one feature yourself first, then get an agent review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weekly:&lt;/strong&gt; Do one bug hunt (practice 6) or one architecture kata (practice 7).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monthly:&lt;/strong&gt; Review your mistakes log and update your rules file.
It adds maybe 2 to 3 hours a week. In return, you grow as an engineer instead of just getting faster at typing prompts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to know if you're learning or just outsourcing
&lt;/h2&gt;

&lt;p&gt;Ask yourself honestly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Could I rebuild last week's feature without the agent, even if it took longer?&lt;/li&gt;
&lt;li&gt;Do I catch the agent's mistakes more often than I did three months ago?&lt;/li&gt;
&lt;li&gt;Can I explain the trade-offs behind our current architecture?&lt;/li&gt;
&lt;li&gt;Am I comfortable reading code in a language I didn't know six months ago?
If the answers are "yes," the agent is making you better. If they're "no," you're renting skill instead of building it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Improve together
&lt;/h2&gt;

&lt;p&gt;Agents aren't going away, and they'll keep getting better. The developers who thrive won't be the ones who write the best prompts. They'll be the ones who &lt;strong&gt;use every interaction as a chance to learn&lt;/strong&gt;, and who can still think clearly when the agent is wrong.&lt;/p&gt;

&lt;p&gt;Don't just give the agent instructions. Grow alongside it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What's one thing an AI agent taught you that actually stuck? Tell me in the comments. I'd love to build a list of learning techniques together.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>career</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Your AI Coding Agent Shouldn't Live on Your Laptop</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Mon, 14 Sep 2026 00:47:19 +0000</pubDate>
      <link>https://dev.to/yorchperaza/your-ai-coding-agent-shouldnt-live-on-your-laptop-166j</link>
      <guid>https://dev.to/yorchperaza/your-ai-coding-agent-shouldnt-live-on-your-laptop-166j</guid>
      <description>&lt;p&gt;Every AI coding tool I have used makes the same assumption: the code is on the laptop you are sitting at.&lt;/p&gt;

&lt;p&gt;That is true for maybe half of what an engineering team actually does. The rest happens on a dev box, in CI, on a staging environment, or on a build machine that has the toolchain your laptop does not.&lt;/p&gt;

&lt;p&gt;We just shipped the MonkeysCode Code Agent, a desktop app for running coding agents across projects without an editor in the way. The most interesting part of it is not the agent loop. It is where the agent is allowed to run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four execution backends
&lt;/h2&gt;

&lt;p&gt;The agent can execute against any of these, and you pick per workspace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local.&lt;/strong&gt; Your machine. Full capability, including semantic search against a local index and interactive shell sessions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSH.&lt;/strong&gt; A remote development box. Every command is translated and executed there, not here. This is the one nobody else offers, and it is the one I use most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloud.&lt;/strong&gt; A managed container workspace, sandboxed by default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remote control.&lt;/strong&gt; A peer machine over WebSocket, running a remote host agent. A build server, a test rig, a colleague's environment.&lt;/p&gt;

&lt;p&gt;Each backend declares its real capabilities honestly. Semantic search is not available over SSH, so the agent's system prompt says so and the UI adapts. It is not allowed to attempt something that will fail and then improvise around the error.&lt;/p&gt;

&lt;p&gt;That last detail matters more than it sounds. A lot of agent frustration comes from the model confidently trying something the environment cannot do, failing, and then trying three more things. Telling it the truth up front is cheaper than letting it discover the constraint.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like for DevOps
&lt;/h2&gt;

&lt;p&gt;Here is the part I think is genuinely underexplored in this category.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI that fixes itself
&lt;/h3&gt;

&lt;p&gt;The CLI runs the same engine headless, with structured output and real exit codes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Attempt automated fix&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;failure()&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;mcode -p "The test suite failed. Read the output, find the cause, fix it." \&lt;/span&gt;
          &lt;span class="s"&gt;--output json \&lt;/span&gt;
          &lt;span class="s"&gt;--permission-mode accept-edits &amp;gt; result.json&lt;/span&gt;
    &lt;span class="s"&gt;echo "exit=$?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because every run is test-gated, a change that does not make the suite green never gets applied. The agent either produces a working fix or it produces nothing. There is no middle state where it commits something plausible and moves on.&lt;/p&gt;

&lt;p&gt;That is what makes this usable unattended. Not the model quality, the guardrail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scheduled maintenance
&lt;/h3&gt;

&lt;p&gt;Background and scheduled runs mean the boring recurring work can happen without a human starting it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Every Monday 06:00
  → Update dependencies, run the suite, open a PR if green
Every night
  → Generate tests for files changed in the last 24h
Every deploy
  → Check the changelog matches what actually shipped
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each one produces a signed record and a diff you review before merging. It is not autonomous merging, it is autonomous preparation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agents on the machine that has the toolchain
&lt;/h3&gt;

&lt;p&gt;This is the SSH case and it comes up constantly.&lt;/p&gt;

&lt;p&gt;Your laptop is macOS on ARM. Production is Linux on x86. The build needs a GPU you do not have, or a database that only exists on the dev box, or a proprietary compiler your security team will not license per-seat.&lt;/p&gt;

&lt;p&gt;Point the agent at the machine where those things live. It reads the real filesystem, runs the real build, sees the real errors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mcode &lt;span class="nt"&gt;--backend&lt;/span&gt; ssh &lt;span class="nt"&gt;--host&lt;/span&gt; devbox-3 &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"The nightly build is failing on the arm64 target. Diagnose it."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No syncing, no containerising your dev environment, no reproducing production locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Many agents, many projects
&lt;/h3&gt;

&lt;p&gt;The desktop app orchestrates parallel runs across independent projects, each in an isolated workspace. A mission board shows what every agent is doing, an inbox collects the ones waiting on a decision, and you review a signed diff and test report before anything merges.&lt;/p&gt;

&lt;p&gt;For anyone maintaining several services, this is the difference between an agent being a tool you use and an agent being work that happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  The guardrails, because autonomy without them is a liability
&lt;/h2&gt;

&lt;p&gt;Four permission modes, and you switch with a keystroke.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Reads&lt;/th&gt;
&lt;th&gt;Edits&lt;/th&gt;
&lt;th&gt;Runs commands&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Plan&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ask&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;on approval&lt;/td&gt;
&lt;td&gt;on approval&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accept edits&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;automatic&lt;/td&gt;
&lt;td&gt;on approval&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;automatic&lt;/td&gt;
&lt;td&gt;automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note that edit permission and command permission are deliberately separate. In practice people trust an agent to modify a file long before they trust it to run something in their shell. Bundling those into one autonomy slider was a mistake we made early and corrected.&lt;/p&gt;

&lt;p&gt;Auto mode is only available inside a sandbox. Not as a policy, as an enforced constraint. Seatbelt on macOS, bubblewrap on Linux, AppContainer on Windows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two engineering details worth stealing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  We did not build undo on git stash
&lt;/h3&gt;

&lt;p&gt;Every file the agent touches is snapshotted before its first mutation, so you can revert one file or an entire run.&lt;/p&gt;

&lt;p&gt;The obvious implementation is &lt;code&gt;git stash push -u&lt;/code&gt;. One command, already there, works.&lt;/p&gt;

&lt;p&gt;It also destroys your own uncommitted work.&lt;/p&gt;

&lt;p&gt;Think about the actual scenario. You have half-finished changes in your tree. You ask an agent to do something unrelated. You do not like the result and you revert. With git stash, your half-finished changes went with it.&lt;/p&gt;

&lt;p&gt;So we built a separate snapshot store. Pre-mutation capture per file, selective revert, workspace-scoped so it cannot reach into another project, and honest about limits: a recursive directory delete is flagged as unrevertable rather than silently skipped.&lt;/p&gt;

&lt;p&gt;More work than one git command. But an undo feature that occasionally destroys your work is not an undo feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Diagnostics report only what the agent broke
&lt;/h3&gt;

&lt;p&gt;After edits, the agent runs your project's type-checker or linter.&lt;/p&gt;

&lt;p&gt;The important part is that it captures a &lt;strong&gt;baseline at run start&lt;/strong&gt; and reports only what is new. If your codebase already has 400 warnings, you do not get 400 warnings. You get the ones it just introduced.&lt;/p&gt;

&lt;p&gt;Anyone who has introduced a linter to a legacy codebase knows why this matters. Signal drowns in noise, people stop reading it, and the tool stops working. A verification step that produces noise is the same as no verification step.&lt;/p&gt;

&lt;p&gt;It also detects the right checker per language, scopes to affected packages in a monorepo rather than checking everything, and retries once on transient failures so a mid-build state does not report a false problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Research that outlives the session
&lt;/h2&gt;

&lt;p&gt;One more thing that does not exist elsewhere as far as I know.&lt;/p&gt;

&lt;p&gt;Some work is not a task. It is a question you chase across days. A performance regression nobody can reproduce. A migration you are scoping. A bug that only appears under load. That is a dozen runs across a week, and by Thursday you have forgotten what you ruled out on Monday.&lt;/p&gt;

&lt;p&gt;Investigations are long-lived threads scoped to a project. Runs link to them, findings accumulate, and the output is real markdown under &lt;code&gt;.monkeyscode/docs/&lt;/code&gt; in your repository. Git-tracked. Editable in any editor. Reviewable in a pull request.&lt;/p&gt;

&lt;p&gt;Not locked in an application database that dies with the tool. If your work lives inside a vendor's storage, you do not own it.&lt;/p&gt;

&lt;h2&gt;
  
  
  On the model
&lt;/h2&gt;

&lt;p&gt;The agent runs on Capuchin, which we train and serve ourselves, or on Claude, Gemini, ChatGPT, or a model on your own hardware. Switching is a setting.&lt;/p&gt;

&lt;p&gt;Capuchin Reason scores 62.1% on SWE-bench Pro. Claude Opus, the current leader, scores 64.3%. We are 2.2 points behind, measured on our own production endpoint at the same quantization and context configuration we actually serve, with the methodology published.&lt;/p&gt;

&lt;p&gt;The number I find more informative: in production, 92.7% of requests run on Capuchin rather than on a frontier model, even though every user can switch with one setting. For most daily work the 2.2 points are not something you feel.&lt;/p&gt;

&lt;p&gt;Owning the model is also why we can support the ones we do not own. Capuchin is cheap enough for us to serve that we can include frontier allowances in the plan and support local models we earn nothing from.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is not finished
&lt;/h2&gt;

&lt;p&gt;Semantic search only works on the local backend. Over SSH or against a cloud container you get grep and file operations, not vector search. The agent is told this explicitly rather than being allowed to try and fail, but it is a real limitation.&lt;/p&gt;

&lt;p&gt;We have not published a cost-per-task figure. Cursor has. We should, and we will, once the measurement is standardised.&lt;/p&gt;

&lt;p&gt;The CLI is shipping shortly rather than shipped.&lt;/p&gt;

&lt;p&gt;We are pre-1.0 and say so.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The Code Agent and the IDE are both included in every plan, including the free tier with your own API keys. Thirty days of unlimited Capuchin, no credit card.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://monkeyscode.com/code-agent" rel="noopener noreferrer"&gt;monkeyscode.com/code-agent&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you try it, I would rather hear what broke than get a signup. Comments are open and I read all of them, particularly if you have opinions about the SSH backend, which is the part I am least sure we have got right.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>ai</category>
      <category>productivity</category>
      <category>cicd</category>
    </item>
    <item>
      <title>Meet Capuchin AI: Fast, Reasoning, and Not the Only Option</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Wed, 29 Jul 2026 01:46:28 +0000</pubDate>
      <link>https://dev.to/yorchperaza/meet-capuchin-ai-fast-reasoning-and-not-the-only-option-10km</link>
      <guid>https://dev.to/yorchperaza/meet-capuchin-ai-fast-reasoning-and-not-the-only-option-10km</guid>
      <description>&lt;p&gt;Capuchin is the AI that powers MonkeysCode by default. This post is about what it actually does, how it's served, and, since we've gotten this question a lot, what it &lt;em&gt;isn't&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two modes, one model
&lt;/h2&gt;

&lt;p&gt;Coding tasks aren't uniform. Autocomplete and small edits need to feel instant. Planning a multi-file refactor or chasing a gnarly bug benefits from actually thinking it through. Most tools pick one tradeoff and apply it everywhere. Capuchin doesn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capuchin Flash.&lt;/strong&gt; Thinking off, sub-second responses. This is the 80% of coding work: completions, small edits, quick questions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capuchin Reason.&lt;/strong&gt; Step-by-step reasoning, budgeted so "thinking" never turns into "spinning." This is the 20% that's actually hard: multi-file plans, debugging, tradeoffs.
MonkeysCode routes between them automatically based on the task, or you can pin one yourself. The point isn't "slow but smart" vs "fast but dumb." It's picking the right tool per step, inside a single session.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why we can afford to include unlimited usage
&lt;/h2&gt;

&lt;p&gt;The honest answer is efficiency. Capuchin is built to activate only a fraction of its total capacity per token. The architecture spends its weights on software-engineering tasks specifically, rather than spreading capacity across everything a general chat model tries to do. That's what makes it both sharper on real coding work and cheap enough to serve that we can bundle generous usage into a subscription instead of metering every token.&lt;/p&gt;

&lt;p&gt;Practically, that shows up as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Speculative decoding and caching&lt;/strong&gt; keep response times low even under load&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A fair-use ceiling&lt;/strong&gt;, not literal infinity. "Unlimited" is generous, not unbounded, so the economics stay sane for everyone
## Built to use tools, not just talk about them&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Capuchin is tuned specifically for agentic work: it calls your terminal, git, test runner, browser, and MCP servers and uses the results accurately across long sessions, the kind of 20-30-tool-call runs that real refactors actually take, not a single-shot completion. It plans before it acts, executes across files in parallel, and runs your tests before handing back. If something fails, it sees the failure and fixes it rather than reporting success prematurely.&lt;/p&gt;

&lt;p&gt;It's also polyglot by design, strong across Python, JavaScript/TypeScript, Go, Rust, Java, and more, because most of us aren't working in a single language, and a model that's only good at one is a model you'll outgrow.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is &lt;em&gt;not&lt;/em&gt;: a local model
&lt;/h2&gt;

&lt;p&gt;This is worth being precise about, because we've seen the confusion. &lt;strong&gt;Capuchin is a managed cloud service&lt;/strong&gt;, the same delivery model as Claude, ChatGPT, or Codex. It is not something you download and run on your laptop.&lt;/p&gt;

&lt;p&gt;If you need fully offline or air-gapped work, that's a real, fully supported path in MonkeysCode. It's just a different one: point the IDE at &lt;strong&gt;your own model&lt;/strong&gt;, local (Ollama, llama.cpp, vLLM) or on your own server. Capuchin is the managed default; your own model is the offline path. Two clean, non-overlapping stories, not one confused one.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it gets better
&lt;/h2&gt;

&lt;p&gt;We start from a strong open foundation and specialize it hard for real software-engineering work inside MonkeysCode, rather than training something narrow from scratch. Improvement after that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Opt-in only.&lt;/strong&gt; Free, Pro, and Team users choose whether usage signals contribute to improving the model. The default is off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never from enterprise, self-hosted, or air-gapped installs.&lt;/strong&gt; No exceptions, regardless of any general opt-in setting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signal over source.&lt;/strong&gt; Where opt-in is given, we prefer derived signals (was the edit kept? did tests pass?) over retaining raw code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gated releases.&lt;/strong&gt; Every new checkpoint has to beat the current one on our internal benchmark before it ships. No regressions allowed through.
## Try it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Capuchin is included with unlimited usage on every paid MonkeysCode plan, and free to try for a month during our founding-user promo.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://monkeyscode.com/promo" rel="noopener noreferrer"&gt;monkeyscode.com/promo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've got questions about the Flash/Reason routing, the fair-use design, or where the local/managed line actually sits, ask away in the comments. Happy to get into the weeds.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Authentication with MonkeysLegion 2.0 + Next.js / React</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Fri, 08 May 2026 02:58:20 +0000</pubDate>
      <link>https://dev.to/yorchperaza/authentication-with-monkeyslegion-20-nextjs-react-2en4</link>
      <guid>https://dev.to/yorchperaza/authentication-with-monkeyslegion-20-nextjs-react-2en4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A complete guide to implementing JWT-based registration, login, session persistence, and token refresh using the MonkeysLegion v2 API with a Next.js (or React) frontend.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;API Endpoints Overview&lt;/li&gt;
&lt;li&gt;Backend Setup (MonkeysLegion)&lt;/li&gt;
&lt;li&gt;Frontend: API Client&lt;/li&gt;
&lt;li&gt;Frontend: Auth Context Provider&lt;/li&gt;
&lt;li&gt;Frontend: Register Page&lt;/li&gt;
&lt;li&gt;Frontend: Login Page&lt;/li&gt;
&lt;li&gt;Frontend: Protected Routes&lt;/li&gt;
&lt;li&gt;Token Refresh Flow&lt;/li&gt;
&lt;li&gt;Common Issues &amp;amp; Solutions&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  API Endpoints Overview
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion v2 exposes the following auth endpoints under the &lt;code&gt;/api/v2/auth&lt;/code&gt; prefix:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Auth&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/auth/register&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Create a new user + company&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/auth/login&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Authenticate and get JWT tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/auth/refresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Exchange refresh token for new access token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/auth/me&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Get authenticated user profile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/auth/logout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Invalidate tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;POST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/auth/forgot-password&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Request password reset&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Response Shapes
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Login Response&lt;/strong&gt; — &lt;code&gt;POST /auth/login&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"access_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJ0eXAiOiJKV1Q..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"refresh_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJ0eXAiOiJKV1Q..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"token_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"expires_in"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"full_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Register Response&lt;/strong&gt; — &lt;code&gt;POST /auth/register&lt;/code&gt; (HTTP 201)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Registration successful"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"full_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Acme Inc."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"access_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJ0eXAiOiJKV1Q..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"refresh_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJ0eXAiOiJKV1Q..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"token_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"expires_in"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1800&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Me Response&lt;/strong&gt; — &lt;code&gt;GET /auth/me&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"full_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"phone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UTC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"two_factor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-08T01:25:23+00:00"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Backend Setup (MonkeysLegion)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Install the Framework
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require monkeyscloud/monkeyslegion:^2.0.8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Version &lt;code&gt;2.0.8+&lt;/code&gt; is required. Earlier versions have a bug in &lt;code&gt;DatabaseUserProvider&lt;/code&gt; where typed properties (&lt;code&gt;DateTimeImmutable&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;bool&lt;/code&gt;) throw &lt;code&gt;TypeError&lt;/code&gt; during hydration from raw PDO strings.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Entity — &lt;code&gt;app/Entity/User.php&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Entity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;MonkeysLegion\Auth\Contract\AuthenticatableInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;MonkeysLegion\Query\Attribute\&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;Entity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Timestamps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Hidden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Fillable&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="na"&gt;#[Entity(table: 'users')]&lt;/span&gt;
&lt;span class="na"&gt;#[Timestamps]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;AuthenticatableInterface&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;#[Id]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;private&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[Field(type: 'string', length: 255, unique: true)]&lt;/span&gt;
    &lt;span class="na"&gt;#[Fillable]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[Field(type: 'string', length: 255)]&lt;/span&gt;
    &lt;span class="na"&gt;#[Fillable]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$full_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[Field(type: 'string', length: 255)]&lt;/span&gt;
    &lt;span class="na"&gt;#[Hidden]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$password_hash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[Field(type: 'integer', default: 1)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$token_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[Field(type: 'datetime')]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;private&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;\DateTimeImmutable&lt;/span&gt; &lt;span class="nv"&gt;$created_at&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="na"&gt;#[Field(type: 'datetime')]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;private&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;\DateTimeImmutable&lt;/span&gt; &lt;span class="nv"&gt;$updated_at&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// ── AuthenticatableInterface ──&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getAuthIdentifier&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getAuthPassword&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;password_hash&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Controller — &lt;code&gt;app/Controller/Api/AuthController.php&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Controller\Api&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Service\AuthService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;MonkeysLegion\Http\Attribute\&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Throttle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Authenticated&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;MonkeysLegion\Http\Message\Response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Psr\Http\Message\ServerRequestInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="na"&gt;#[Prefix('/api/v2/auth')]&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;AuthService&lt;/span&gt; &lt;span class="nv"&gt;$auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="na"&gt;#[Route('POST', '/login', name: 'auth.login')]&lt;/span&gt;
    &lt;span class="na"&gt;#[Throttle(max: 5, per: 60)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ServerRequestInterface&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getBody&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Missing credentials'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Invalid credentials'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'data'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'access_token'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'access_token'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;'refresh_token'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'refresh_token'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;'token_type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Bearer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'expires_in'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'expires_in'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;'user'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="s1"&gt;'id'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'email'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'full_name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="na"&gt;#[Route('POST', '/register', name: 'auth.register')]&lt;/span&gt;
    &lt;span class="na"&gt;#[Throttle(max: 3, per: 60)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ServerRequestInterface&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getBody&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Missing required fields'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Check for duplicate email&lt;/span&gt;
        &lt;span class="nv"&gt;$existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;findByEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$existing&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
                &lt;span class="s1"&gt;'error'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Validation failed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'details'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Email already registered'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;422&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'data'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'message'&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Registration successful'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'user'&lt;/span&gt;          &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="s1"&gt;'id'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'email'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'full_name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;'access_token'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'access_token'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;'refresh_token'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'refresh_token'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="s1"&gt;'token_type'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Bearer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'expires_in'&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'expires_in'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="na"&gt;#[Route('GET', '/me', name: 'auth.me')]&lt;/span&gt;
    &lt;span class="na"&gt;#[Authenticated]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;me&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ServerRequestInterface&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;Response&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// NOTE: The attribute is 'auth.user', NOT 'user'&lt;/span&gt;
        &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'auth.user'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="s1"&gt;'data'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'id'&lt;/span&gt;         &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'email'&lt;/span&gt;      &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'full_name'&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'status'&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;'created_at'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'c'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Always use &lt;code&gt;$request-&amp;gt;getAttribute('auth.user')&lt;/code&gt; — the &lt;code&gt;AuthenticationMiddleware&lt;/code&gt; sets the attribute as &lt;code&gt;auth.user&lt;/code&gt;, not &lt;code&gt;user&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Frontend: API Client
&lt;/h2&gt;

&lt;p&gt;Create a reusable API client that handles JWT tokens, automatic refresh, and typed requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/lib/api.ts&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;API_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_API_URL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:8088&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ApiOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;method&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;body&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;token&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ApiClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;getToken&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApiOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getToken&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;authToken&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;authToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Auto-refresh on 401&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;refreshed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;refreshed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getToken&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearTokens&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusText&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Request failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;refresh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_refresh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/v2/auth/refresh`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;refresh&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_refresh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;clearTokens&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_refresh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ── Auth Methods ──────────────────────────────────────────&lt;/span&gt;
  &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v2/auth/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v2/auth/register&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;company_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;companyName&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApiClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Frontend: Auth Context Provider
&lt;/h2&gt;

&lt;p&gt;Wrap your app with &lt;code&gt;AuthProvider&lt;/code&gt; to share auth state across all components.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/lib/auth.tsx&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;createContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AuthState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;register&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AuthContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AuthState&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AuthProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// On mount: validate stored token by calling /me&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;api&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/v2/auth/me&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
          &lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_refresh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_refresh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Map full_name → name for frontend consistency&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;full_name&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_refresh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;full_name&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mm_refresh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AuthContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;AuthContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useAuth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AuthContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;useAuth must be used within AuthProvider&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The API returns &lt;code&gt;full_name&lt;/code&gt; but we map it to &lt;code&gt;name&lt;/code&gt; in the frontend &lt;code&gt;User&lt;/code&gt; interface for simplicity. This mapping happens in three places: &lt;code&gt;login&lt;/code&gt;, &lt;code&gt;register&lt;/code&gt;, and the &lt;code&gt;/me&lt;/code&gt; response handler.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Wire Into Layout — &lt;code&gt;src/app/layout.tsx&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AuthProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RootLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AuthProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;AuthProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Frontend: Register Page
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/app/(auth)/register/page.tsx&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAuth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RegisterPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setEmail&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPassword&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCompanyName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FormEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Redirect to login after registration&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Registration failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Create Account&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;     &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;        &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;        &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Full name"&lt;/span&gt;    &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;       &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;       &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Email"&lt;/span&gt;        &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;    &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;    &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt;     &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;     &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;companyName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setCompanyName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Company name"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Creating...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Create Account&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Already have an account? &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/login"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Sign in&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Frontend: Login Page
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/app/(auth)/login/page.tsx&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAuth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;LoginPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;login&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setEmail&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPassword&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FormEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Redirect to dashboard&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Login failed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Welcome Back&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;    &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;    &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Email"&lt;/span&gt;    &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Password"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Signing in...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sign in&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Don't have an account? &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/register"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Create one&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Frontend: Protected Routes
&lt;/h2&gt;

&lt;p&gt;Wrap dashboard pages with a layout that redirects unauthenticated users.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;src/app/(dashboard)/layout.tsx&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRouter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAuth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;DashboardLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRouter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using the User in Components
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useAuth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProfileCard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logout&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Welcome, &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;logout&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Sign Out&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Token Refresh Flow
&lt;/h2&gt;

&lt;p&gt;The API client handles token refresh &lt;strong&gt;automatically&lt;/strong&gt;. Here's the sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Browser makes a request (e.g. &lt;code&gt;GET /api/v2/messages&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;API Client attaches the stored &lt;code&gt;Bearer&lt;/code&gt; token&lt;/li&gt;
&lt;li&gt;If the API returns &lt;code&gt;401 Unauthorized&lt;/code&gt; (token expired):

&lt;ul&gt;
&lt;li&gt;Client sends &lt;code&gt;POST /api/v2/auth/refresh&lt;/code&gt; with the refresh token&lt;/li&gt;
&lt;li&gt;On success: stores the new tokens and &lt;strong&gt;retries the original request&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;On failure: clears all tokens and redirects to &lt;code&gt;/login&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Token Lifetimes
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Token&lt;/th&gt;
&lt;th&gt;Lifetime&lt;/th&gt;
&lt;th&gt;Storage Key&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Access Token&lt;/td&gt;
&lt;td&gt;30 minutes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;localStorage("mm_token")&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refresh Token&lt;/td&gt;
&lt;td&gt;7 days&lt;/td&gt;
&lt;td&gt;&lt;code&gt;localStorage("mm_refresh")&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; If both tokens are expired (user inactive for 7+ days), the client clears storage and redirects to &lt;code&gt;/login&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Common Issues &amp;amp; Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;TypeError: Cannot assign string to property ... of type DateTimeImmutable&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; &lt;code&gt;DatabaseUserProvider&lt;/code&gt; in MonkeysLegion ≤ 2.0.7 does raw assignment from PDO strings to typed properties.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Upgrade to &lt;code&gt;monkeyscloud/monkeyslegion:^2.0.8&lt;/code&gt; which includes &lt;code&gt;castValue()&lt;/code&gt; type coercion in the hydrator.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. &lt;code&gt;/me&lt;/code&gt; returns &lt;code&gt;null&lt;/code&gt; user — &lt;code&gt;$request-&amp;gt;getAttribute('user')&lt;/code&gt; is null
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The &lt;code&gt;AuthenticationMiddleware&lt;/code&gt; sets the attribute as &lt;code&gt;auth.user&lt;/code&gt;, not &lt;code&gt;user&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- $user = $request-&amp;gt;getAttribute('user');
&lt;/span&gt;&lt;span class="gi"&gt;+ $user = $request-&amp;gt;getAttribute('auth.user');
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  3. &lt;code&gt;422 Unprocessable Content&lt;/code&gt; on register
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The email is already registered. The API returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Validation failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Email already registered"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Use a different email, or parse the error details in the frontend to show a specific message.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. &lt;code&gt;full_name&lt;/code&gt; vs &lt;code&gt;name&lt;/code&gt; mismatch
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The API returns &lt;code&gt;full_name&lt;/code&gt; but your frontend &lt;code&gt;User&lt;/code&gt; interface expects &lt;code&gt;name&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Map the field in every place you read user data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;full_name&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  5. CORS errors from &lt;code&gt;localhost:3000&lt;/code&gt; → &lt;code&gt;localhost:8088&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The API needs CORS headers for cross-origin requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Add CORS middleware in your MonkeysLegion middleware stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/middleware.mlc&lt;/span&gt;
&lt;span class="nc"&gt;MonkeysLegion\Http\Middleware\CorsMiddleware&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or set the environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CORS_ORIGINS=http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  6. Page redirects to &lt;code&gt;/login&lt;/code&gt; on refresh despite being logged in
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; The &lt;code&gt;/me&lt;/code&gt; endpoint crashes (often the &lt;code&gt;DateTimeImmutable&lt;/code&gt; bug), so the auth provider clears tokens and sets &lt;code&gt;user = null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Upgrade MonkeysLegion to &lt;code&gt;2.0.8+&lt;/code&gt; and verify &lt;code&gt;/me&lt;/code&gt; works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:8088/api/v2/auth/me &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_TOKEN"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Quick Reference
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Register a user via curl&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8088/api/v2/auth/register &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "name": "John Doe",
    "email": "john@example.com",
    "password": "securepass123",
    "company_name": "Acme Inc."
  }'&lt;/span&gt;

&lt;span class="c"&gt;# Login&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8088/api/v2/auth/login &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"email": "john@example.com", "password": "securepass123"}'&lt;/span&gt;

&lt;span class="c"&gt;# Get current user&lt;/span&gt;
curl http://localhost:8088/api/v2/auth/me &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &amp;lt;access_token&amp;gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Refresh token&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8088/api/v2/auth/refresh &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"refresh_token": "&amp;lt;refresh_token&amp;gt;"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://monkeyslegion.com/" rel="noopener noreferrer"&gt;MonkeysLegion&lt;/a&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>symfony</category>
      <category>react</category>
    </item>
    <item>
      <title>MonkeysLegion 2.0 Is Released — Introducing Apex AI and a Faster PHP Runtime</title>
      <dc:creator>Jorge Peraza</dc:creator>
      <pubDate>Tue, 28 Apr 2026 22:43:51 +0000</pubDate>
      <link>https://dev.to/yorchperaza/monkeyslegion-20-is-released-introducing-apex-ai-and-a-faster-php-runtime-1m57</link>
      <guid>https://dev.to/yorchperaza/monkeyslegion-20-is-released-introducing-apex-ai-and-a-faster-php-runtime-1m57</guid>
      <description>&lt;p&gt;MonkeysLegion 2.0 is officially released.&lt;/p&gt;

&lt;p&gt;This is more than a framework update. It is a full architectural step forward for the MonkeysLegion ecosystem: a faster runtime, a cleaner PHP 8.4+ foundation, a stronger modular package structure, and a new AI orchestration layer built directly for modern PHP applications.&lt;/p&gt;

&lt;p&gt;PHP has always been one of the most productive languages on the web.&lt;/p&gt;

&lt;p&gt;It powers SaaS platforms, APIs, internal tools, content systems, enterprise applications, dashboards, automation workflows, and products used by millions of people every day.&lt;/p&gt;

&lt;p&gt;But modern teams are building in a different environment now.&lt;/p&gt;

&lt;p&gt;They need faster boot times.&lt;br&gt;&lt;br&gt;
They need clean architecture.&lt;br&gt;&lt;br&gt;
They need APIs by default.&lt;br&gt;&lt;br&gt;
They need cloud-native deployment.&lt;br&gt;&lt;br&gt;
They need observability.&lt;br&gt;&lt;br&gt;
They need DevOps-friendly workflows.&lt;br&gt;&lt;br&gt;
They need AI-ready applications.&lt;br&gt;&lt;br&gt;
They need developer experience without hidden runtime magic.  &lt;/p&gt;

&lt;p&gt;That is why we are releasing &lt;strong&gt;MonkeysLegion 2.0&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;MonkeysLegion 2.0 is built for developers and teams who want the clarity of raw PHP, the structure of a serious framework, the productivity of a modular ecosystem, and the performance profile of a runtime that gets out of the way.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why MonkeysLegion 2.0 Matters
&lt;/h2&gt;

&lt;p&gt;Laravel built an incredible ecosystem.&lt;/p&gt;

&lt;p&gt;Forge, Vapor, Nova, Cashier, queues, jobs, billing tools, starter kits, and one of the largest PHP communities in the world. If you need a batteries-included SaaS starter, Laravel is hard to beat.&lt;/p&gt;

&lt;p&gt;Symfony powers some of the most mission-critical enterprise systems on earth.&lt;/p&gt;

&lt;p&gt;Its profiler, debug toolbar, reusable components, release discipline, and enterprise credibility are best-in-class.&lt;/p&gt;

&lt;p&gt;MonkeysLegion is built for a different moment.&lt;/p&gt;

&lt;p&gt;A moment where teams want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Raw throughput&lt;/li&gt;
&lt;li&gt;PHP 8.4+ architecture&lt;/li&gt;
&lt;li&gt;Attribute-first development&lt;/li&gt;
&lt;li&gt;Minimal runtime overhead&lt;/li&gt;
&lt;li&gt;Clean dependency injection&lt;/li&gt;
&lt;li&gt;Modular packages&lt;/li&gt;
&lt;li&gt;API-first structure&lt;/li&gt;
&lt;li&gt;DevOps automation&lt;/li&gt;
&lt;li&gt;Cloud-native deployment&lt;/li&gt;
&lt;li&gt;Observability&lt;/li&gt;
&lt;li&gt;AI orchestration&lt;/li&gt;
&lt;li&gt;Enterprise-ready foundations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No unnecessary heavy boot process.&lt;br&gt;&lt;br&gt;
No runtime proxies.&lt;br&gt;&lt;br&gt;
No hidden magic.&lt;br&gt;&lt;br&gt;
No fighting the framework.&lt;/p&gt;

&lt;p&gt;Just your code, clean architecture, and a framework designed to move fast from local development to production.&lt;/p&gt;


&lt;h2&gt;
  
  
  What Changed in 2.0
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion 2.0 is a full architectural pass across the framework and package ecosystem.&lt;/p&gt;

&lt;p&gt;The release focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PHP 8.4+ native architecture&lt;/li&gt;
&lt;li&gt;Attribute-first routing and configuration&lt;/li&gt;
&lt;li&gt;Compiled dependency injection&lt;/li&gt;
&lt;li&gt;Typed MLC configuration&lt;/li&gt;
&lt;li&gt;PSR-compliant HTTP and middleware layers&lt;/li&gt;
&lt;li&gt;Production-ready security middleware&lt;/li&gt;
&lt;li&gt;OpenAPI-first API development&lt;/li&gt;
&lt;li&gt;A modular package ecosystem&lt;/li&gt;
&lt;li&gt;Better developer tooling&lt;/li&gt;
&lt;li&gt;Faster runtime behavior&lt;/li&gt;
&lt;li&gt;AI orchestration with Apex AI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make PHP feel modern again — fast, clean, modular, typed, observable, deployable, and ready for AI-native applications.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  PHP 8.4+ Native Architecture
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion 2.0 is designed around modern PHP instead of legacy conventions.&lt;/p&gt;

&lt;p&gt;The framework is moving deeper into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strict types&lt;/li&gt;
&lt;li&gt;PHP attributes&lt;/li&gt;
&lt;li&gt;Property hooks&lt;/li&gt;
&lt;li&gt;Constructor injection&lt;/li&gt;
&lt;li&gt;Typed configuration&lt;/li&gt;
&lt;li&gt;Compiled containers&lt;/li&gt;
&lt;li&gt;PSR standards&lt;/li&gt;
&lt;li&gt;Clean service boundaries&lt;/li&gt;
&lt;li&gt;Lightweight runtime behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main idea is that your application should be readable from the code itself.&lt;/p&gt;

&lt;p&gt;Routes should be visible.&lt;br&gt;&lt;br&gt;
Services should be explicit.&lt;br&gt;&lt;br&gt;
Configuration should be understandable.&lt;br&gt;&lt;br&gt;
Controllers should be clean.&lt;br&gt;&lt;br&gt;
Database logic should be predictable.&lt;br&gt;&lt;br&gt;
Deployment should not require guessing.&lt;/p&gt;

&lt;p&gt;MonkeysLegion 2.0 keeps the code close to the developer.&lt;/p&gt;


&lt;h2&gt;
  
  
  Attribute-First Development
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion 2.0 uses PHP attributes as a first-class architecture pattern.&lt;/p&gt;

&lt;p&gt;Routes, validation rules, service providers, commands, middleware, and AI tools can be declared close to the code they affect.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;MonkeysLegion\Http\Attributes\Route&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Psr\Http\Message\ResponseInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProjectController&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;#[Route('/api/projects', methods: ['GET'])]&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;ResponseInterface&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;list&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes applications easier to understand.&lt;/p&gt;

&lt;p&gt;You open a controller and immediately see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The endpoint&lt;/li&gt;
&lt;li&gt;The HTTP method&lt;/li&gt;
&lt;li&gt;The handler&lt;/li&gt;
&lt;li&gt;The dependencies&lt;/li&gt;
&lt;li&gt;The response flow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For teams building APIs, dashboards, SaaS platforms, internal tools, CMS platforms, or microservices, this matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  Compiled Dependency Injection
&lt;/h2&gt;

&lt;p&gt;Dependency injection should help developers, not confuse them.&lt;/p&gt;

&lt;p&gt;MonkeysLegion 2.0 continues the direction of a clean, predictable container that favors explicit services, typed dependencies, and production compilation.&lt;/p&gt;

&lt;p&gt;The goal is not to create a magical container that hides everything.&lt;/p&gt;

&lt;p&gt;The goal is to make dependency management simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Psr\Log\LoggerInterface&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProjectService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;ProjectRepository&lt;/span&gt; &lt;span class="nv"&gt;$projects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;LoggerInterface&lt;/span&gt; &lt;span class="nv"&gt;$logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your services are clear.&lt;br&gt;&lt;br&gt;
Your dependencies are explicit.&lt;br&gt;&lt;br&gt;
Your application is easier to test.&lt;br&gt;&lt;br&gt;
Your architecture is easier to reason about.&lt;/p&gt;

&lt;p&gt;In production, the framework can compile container definitions so applications avoid unnecessary runtime reflection on the hot path.&lt;/p&gt;

&lt;p&gt;That is one of the core performance principles behind MonkeysLegion 2.0.&lt;/p&gt;


&lt;h2&gt;
  
  
  Performance Is a Core Feature
&lt;/h2&gt;

&lt;p&gt;Performance is not a marketing layer in MonkeysLegion 2.0.&lt;/p&gt;

&lt;p&gt;It is part of the architecture.&lt;/p&gt;

&lt;p&gt;The framework is designed around zero-magic PHP objects, compiled routing, compiled dependency injection, attribute discovery at cache/build time, and a lean PSR middleware pipeline.&lt;/p&gt;

&lt;p&gt;The performance direction is based on several key principles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Zero-magic architecture&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Entities, DTOs, resources, and enums should behave like plain PHP objects.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No proxy-heavy runtime model&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The framework avoids forcing every application through runtime proxy layers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compiled routing and DI&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Discovery happens before production runtime where possible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lean middleware pipeline&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Middleware should be powerful, but the request path should remain predictable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;PHP 8.4+ features&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The framework embraces modern PHP language improvements instead of simulating them with legacy patterns.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On the current MonkeysLegion benchmarks, the framework is positioned around major improvements over heavier runtime approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Entity creation: around &lt;strong&gt;140× faster than Laravel&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;DTO construction: around &lt;strong&gt;60× faster than Laravel&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Enum operations: around &lt;strong&gt;25× faster than Laravel&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Resource serialization: around &lt;strong&gt;5.5× faster than Laravel&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;HTTP throughput: around &lt;strong&gt;12.5K requests/sec vs Laravel’s ~2.1K requests/sec&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Cold boot memory: significantly lower than heavier full-stack boot processes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These benchmark numbers should be understood in context: benchmarks depend on hardware, PHP version, configuration, JIT, opcache, test harness, and workload.&lt;/p&gt;

&lt;p&gt;But the architectural direction is clear:&lt;/p&gt;

&lt;p&gt;MonkeysLegion 2.0 is designed to reduce runtime overhead and make high-performance PHP applications easier to build.&lt;/p&gt;


&lt;h2&gt;
  
  
  API-First by Default
&lt;/h2&gt;

&lt;p&gt;Modern applications are API-driven.&lt;/p&gt;

&lt;p&gt;A backend is rarely just a backend anymore. It may serve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A web app&lt;/li&gt;
&lt;li&gt;A mobile app&lt;/li&gt;
&lt;li&gt;A dashboard&lt;/li&gt;
&lt;li&gt;A CMS&lt;/li&gt;
&lt;li&gt;A CLI&lt;/li&gt;
&lt;li&gt;A partner integration&lt;/li&gt;
&lt;li&gt;An AI workflow&lt;/li&gt;
&lt;li&gt;A microservice&lt;/li&gt;
&lt;li&gt;A third-party API consumer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MonkeysLegion 2.0 is designed with APIs as a first-class concern.&lt;/p&gt;

&lt;p&gt;The framework and skeleton are moving toward a complete API foundation with support for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PSR-7 and PSR-15 HTTP handling&lt;/li&gt;
&lt;li&gt;Middleware pipelines&lt;/li&gt;
&lt;li&gt;JWT authentication&lt;/li&gt;
&lt;li&gt;RBAC&lt;/li&gt;
&lt;li&gt;API keys&lt;/li&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;DTO binding&lt;/li&gt;
&lt;li&gt;OpenAPI documentation&lt;/li&gt;
&lt;li&gt;Swagger UI&lt;/li&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;li&gt;Security headers&lt;/li&gt;
&lt;li&gt;Request IDs&lt;/li&gt;
&lt;li&gt;CORS&lt;/li&gt;
&lt;li&gt;Trusted proxies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means teams can build real production APIs without stitching together half a dozen disconnected tools.&lt;/p&gt;


&lt;h2&gt;
  
  
  Introducing Apex AI
&lt;/h2&gt;

&lt;p&gt;The biggest new direction in the MonkeysLegion ecosystem is &lt;strong&gt;Apex AI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Apex AI is an AI orchestration package for PHP 8.4+.&lt;/p&gt;

&lt;p&gt;It is not just another OpenAI wrapper.&lt;/p&gt;

&lt;p&gt;Apex AI is designed to bring production-ready AI infrastructure into PHP with a clean, framework-native developer experience.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build AI-powered applications in PHP without stitching together multiple disconnected tools.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Apex AI introduces a unified way to work with multiple AI providers, route requests intelligently, build pipelines, manage cost, use tools, stream responses, and create real AI workflows inside PHP applications.&lt;/p&gt;


&lt;h2&gt;
  
  
  What Apex AI Brings to PHP
&lt;/h2&gt;

&lt;p&gt;Apex AI is designed around the needs of real production applications.&lt;/p&gt;

&lt;p&gt;It focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unified AI provider interface&lt;/li&gt;
&lt;li&gt;Multi-provider routing&lt;/li&gt;
&lt;li&gt;OpenAI support&lt;/li&gt;
&lt;li&gt;Anthropic support&lt;/li&gt;
&lt;li&gt;Google AI Studio support&lt;/li&gt;
&lt;li&gt;Vertex AI support&lt;/li&gt;
&lt;li&gt;Ollama/local model support&lt;/li&gt;
&lt;li&gt;Smart model routing&lt;/li&gt;
&lt;li&gt;Fallback chains&lt;/li&gt;
&lt;li&gt;Declarative AI pipelines&lt;/li&gt;
&lt;li&gt;Guardrails&lt;/li&gt;
&lt;li&gt;Structured outputs&lt;/li&gt;
&lt;li&gt;Tool calling with PHP attributes&lt;/li&gt;
&lt;li&gt;Streaming with Server-Sent Events&lt;/li&gt;
&lt;li&gt;Agent-style workflows&lt;/li&gt;
&lt;li&gt;Multi-agent crews&lt;/li&gt;
&lt;li&gt;Memory strategies&lt;/li&gt;
&lt;li&gt;Cost tracking&lt;/li&gt;
&lt;li&gt;Budget controls&lt;/li&gt;
&lt;li&gt;Model Context Protocol support&lt;/li&gt;
&lt;li&gt;Clean integration with MonkeysLegion applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters because AI development is moving beyond simple prompt calls.&lt;/p&gt;

&lt;p&gt;Modern AI applications need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Routing&lt;/li&gt;
&lt;li&gt;Retries&lt;/li&gt;
&lt;li&gt;Fallbacks&lt;/li&gt;
&lt;li&gt;Guardrails&lt;/li&gt;
&lt;li&gt;Tool execution&lt;/li&gt;
&lt;li&gt;Structured responses&lt;/li&gt;
&lt;li&gt;Memory&lt;/li&gt;
&lt;li&gt;Context management&lt;/li&gt;
&lt;li&gt;Cost controls&lt;/li&gt;
&lt;li&gt;Streaming&lt;/li&gt;
&lt;li&gt;Evaluation&lt;/li&gt;
&lt;li&gt;Observability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apex AI brings these ideas into PHP as part of the MonkeysLegion ecosystem.&lt;/p&gt;


&lt;h2&gt;
  
  
  AI-Native PHP Applications
&lt;/h2&gt;

&lt;p&gt;PHP developers should not need to leave PHP to build serious AI workflows.&lt;/p&gt;

&lt;p&gt;Many companies already have PHP backends, PHP teams, PHP infrastructure, PHP CMS platforms, PHP APIs, and PHP business logic.&lt;/p&gt;

&lt;p&gt;The question is not whether PHP can be used for AI applications.&lt;/p&gt;

&lt;p&gt;The question is whether the ecosystem gives PHP developers the right tools.&lt;/p&gt;

&lt;p&gt;Apex AI is built around that belief.&lt;/p&gt;

&lt;p&gt;With Apex AI, a PHP application can become:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An AI-powered CMS&lt;/li&gt;
&lt;li&gt;An AI-assisted dashboard&lt;/li&gt;
&lt;li&gt;A document processing system&lt;/li&gt;
&lt;li&gt;A content generation platform&lt;/li&gt;
&lt;li&gt;A support automation tool&lt;/li&gt;
&lt;li&gt;An internal agent workflow&lt;/li&gt;
&lt;li&gt;A SaaS product with AI features&lt;/li&gt;
&lt;li&gt;A developer platform with AI orchestration&lt;/li&gt;
&lt;li&gt;A backend that routes between cloud and local models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where MonkeysLegion 2.0 becomes more than a framework release.&lt;/p&gt;

&lt;p&gt;It becomes the foundation for AI-native PHP development.&lt;/p&gt;


&lt;h2&gt;
  
  
  Example: Tool Calling with Attributes
&lt;/h2&gt;

&lt;p&gt;Apex AI is designed to feel natural inside modern PHP.&lt;/p&gt;

&lt;p&gt;Instead of forcing AI workflows into disconnected configuration files, tools can be represented as PHP code.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;MonkeysLegion\Apex\Attributes\Tool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProjectTools&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'find_project'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'Find a project by ID and return its current status.'&lt;/span&gt;
  &lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;findProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;findStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps AI tools close to the domain logic of the application.&lt;/p&gt;

&lt;p&gt;The result is easier to read, easier to test, and easier to maintain.&lt;/p&gt;




&lt;h2&gt;
  
  
  Built for SaaS, Internal Tools, and Enterprise Platforms
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion 2.0 is not only for small apps.&lt;/p&gt;

&lt;p&gt;It is being designed for companies building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SaaS platforms&lt;/li&gt;
&lt;li&gt;Internal business tools&lt;/li&gt;
&lt;li&gt;Developer portals&lt;/li&gt;
&lt;li&gt;Admin dashboards&lt;/li&gt;
&lt;li&gt;API platforms&lt;/li&gt;
&lt;li&gt;Automation systems&lt;/li&gt;
&lt;li&gt;AI-enabled products&lt;/li&gt;
&lt;li&gt;Cloud-native services&lt;/li&gt;
&lt;li&gt;CMS-driven websites&lt;/li&gt;
&lt;li&gt;Enterprise applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many companies do not need another heavy framework that assumes one way of working.&lt;/p&gt;

&lt;p&gt;They need a framework that lets them move fast while keeping the codebase clean.&lt;/p&gt;

&lt;p&gt;MonkeysLegion gives teams a structured foundation without forcing unnecessary complexity.&lt;/p&gt;

&lt;p&gt;Apex AI extends that foundation into AI-powered product development.&lt;/p&gt;




&lt;h2&gt;
  
  
  Modular Ecosystem
&lt;/h2&gt;

&lt;p&gt;One of the most important ideas behind MonkeysLegion is modularity.&lt;/p&gt;

&lt;p&gt;You should not need to install a giant framework if you only need one part of it.&lt;/p&gt;

&lt;p&gt;MonkeysLegion is moving toward a package ecosystem where teams can use the pieces they need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Core&lt;/li&gt;
&lt;li&gt;Router&lt;/li&gt;
&lt;li&gt;Dependency Injection&lt;/li&gt;
&lt;li&gt;HTTP&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;Database&lt;/li&gt;
&lt;li&gt;Query Builder&lt;/li&gt;
&lt;li&gt;Migration&lt;/li&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;Internationalization&lt;/li&gt;
&lt;li&gt;Telemetry&lt;/li&gt;
&lt;li&gt;DevTools&lt;/li&gt;
&lt;li&gt;CLI&lt;/li&gt;
&lt;li&gt;GraphQL&lt;/li&gt;
&lt;li&gt;Apex AI&lt;/li&gt;
&lt;li&gt;Cloud tooling&lt;/li&gt;
&lt;li&gt;Testing utilities&lt;/li&gt;
&lt;li&gt;CMS-related packages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This package-first approach makes the framework flexible.&lt;/p&gt;

&lt;p&gt;Small projects can stay small.&lt;br&gt;&lt;br&gt;
Large projects can grow with structure.&lt;br&gt;&lt;br&gt;
Enterprise teams can standardize around the packages that matter to them.&lt;/p&gt;


&lt;h2&gt;
  
  
  Telemetry and Observability
&lt;/h2&gt;

&lt;p&gt;Modern applications need visibility.&lt;/p&gt;

&lt;p&gt;It is not enough for a framework to run code. Teams need to understand what is happening in production.&lt;/p&gt;

&lt;p&gt;MonkeysLegion 2.0 treats observability as part of the developer platform, not as an afterthought.&lt;/p&gt;

&lt;p&gt;That includes visibility into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request performance&lt;/li&gt;
&lt;li&gt;Application health&lt;/li&gt;
&lt;li&gt;Security events&lt;/li&gt;
&lt;li&gt;API usage&lt;/li&gt;
&lt;li&gt;Background processes&lt;/li&gt;
&lt;li&gt;AI requests&lt;/li&gt;
&lt;li&gt;Model routing&lt;/li&gt;
&lt;li&gt;Provider fallbacks&lt;/li&gt;
&lt;li&gt;Cost tracking&lt;/li&gt;
&lt;li&gt;Latency&lt;/li&gt;
&lt;li&gt;Error rates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This becomes even more important with Apex AI.&lt;/p&gt;

&lt;p&gt;AI applications need observability because model behavior, token usage, latency, provider availability, and costs all affect production reliability.&lt;/p&gt;


&lt;h2&gt;
  
  
  Cloud-Native From the Start
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion is not being designed only for local development.&lt;/p&gt;

&lt;p&gt;It is being built with deployment in mind.&lt;/p&gt;

&lt;p&gt;Modern teams need applications that can move from local development to containers, servers, CI/CD pipelines, and cloud environments without massive rewrites.&lt;/p&gt;

&lt;p&gt;That matters because software delivery is no longer just about writing PHP classes.&lt;/p&gt;

&lt;p&gt;It is about the full lifecycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;li&gt;Package&lt;/li&gt;
&lt;li&gt;Deploy&lt;/li&gt;
&lt;li&gt;Monitor&lt;/li&gt;
&lt;li&gt;Scale&lt;/li&gt;
&lt;li&gt;Improve&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;MonkeysLegion 2.0 is being shaped around that complete workflow.&lt;/p&gt;


&lt;h2&gt;
  
  
  A Better Developer Experience Without Heavy Runtime Cost
&lt;/h2&gt;

&lt;p&gt;Developer experience matters.&lt;/p&gt;

&lt;p&gt;But developer experience should not require hiding everything behind magic.&lt;/p&gt;

&lt;p&gt;MonkeysLegion 2.0 is focused on a balanced philosophy:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Give developers speed, structure, and useful defaults — without making the runtime heavy or the code difficult to understand.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear file structure&lt;/li&gt;
&lt;li&gt;Fast boot process&lt;/li&gt;
&lt;li&gt;Explicit dependencies&lt;/li&gt;
&lt;li&gt;Attribute-based configuration&lt;/li&gt;
&lt;li&gt;Useful CLI commands&lt;/li&gt;
&lt;li&gt;Strong defaults&lt;/li&gt;
&lt;li&gt;Modular packages&lt;/li&gt;
&lt;li&gt;Production-ready skeleton&lt;/li&gt;
&lt;li&gt;Easy local setup&lt;/li&gt;
&lt;li&gt;Cloud-ready conventions&lt;/li&gt;
&lt;li&gt;AI-ready workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The framework should feel productive without becoming opaque.&lt;/p&gt;


&lt;h2&gt;
  
  
  Inspired by the Best, Built With a Different Philosophy
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion is not trying to replace Laravel or Symfony.&lt;/p&gt;

&lt;p&gt;They are excellent frameworks with massive ecosystems and proven track records.&lt;/p&gt;

&lt;p&gt;But not every team wants the same tradeoffs.&lt;/p&gt;

&lt;p&gt;Laravel is great when you want a large ecosystem and rapid application scaffolding.&lt;/p&gt;

&lt;p&gt;Symfony is great when you want enterprise components, stability, and deep configurability.&lt;/p&gt;

&lt;p&gt;MonkeysLegion is for teams that want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A lighter framework&lt;/li&gt;
&lt;li&gt;Less magic&lt;/li&gt;
&lt;li&gt;More direct control&lt;/li&gt;
&lt;li&gt;Modern PHP-first architecture&lt;/li&gt;
&lt;li&gt;Fast APIs&lt;/li&gt;
&lt;li&gt;Modular packages&lt;/li&gt;
&lt;li&gt;Cloud-native defaults&lt;/li&gt;
&lt;li&gt;Strong developer tooling&lt;/li&gt;
&lt;li&gt;Clean enterprise structure&lt;/li&gt;
&lt;li&gt;AI orchestration built into the ecosystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not about copying the old model.&lt;/p&gt;

&lt;p&gt;It is about building a framework for where PHP is going next.&lt;/p&gt;


&lt;h2&gt;
  
  
  Built as an Ecosystem, Not Just a Framework
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion 2.0 is part of a bigger vision.&lt;/p&gt;

&lt;p&gt;The framework is the foundation, but the ecosystem is the real goal.&lt;/p&gt;

&lt;p&gt;That ecosystem includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MonkeysLegion Framework&lt;/li&gt;
&lt;li&gt;MonkeysLegion Skeleton&lt;/li&gt;
&lt;li&gt;MonkeysLegion DevTools&lt;/li&gt;
&lt;li&gt;MonkeysLegion GraphQL&lt;/li&gt;
&lt;li&gt;MonkeysLegion Telemetry&lt;/li&gt;
&lt;li&gt;MonkeysLegion Apex AI&lt;/li&gt;
&lt;li&gt;MonkeysCMS&lt;/li&gt;
&lt;li&gt;Cloud deployment tooling&lt;/li&gt;
&lt;li&gt;AI-ready developer workflows&lt;/li&gt;
&lt;li&gt;Enterprise package roadmap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters because modern teams do not only need a router or container.&lt;/p&gt;

&lt;p&gt;They need a complete development platform.&lt;/p&gt;

&lt;p&gt;MonkeysLegion is moving toward that full-stack ecosystem while keeping the core lightweight and modular.&lt;/p&gt;


&lt;h2&gt;
  
  
  MonkeysCMS and the Future of AI-Ready Content Platforms
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion also powers the direction behind MonkeysCMS.&lt;/p&gt;

&lt;p&gt;MonkeysCMS is being designed as a modern, code-first CMS built on top of the MonkeysLegion framework.&lt;/p&gt;

&lt;p&gt;This is one of the strongest examples of why MonkeysLegion matters.&lt;/p&gt;

&lt;p&gt;A framework is not only valuable when it can serve a JSON response.&lt;/p&gt;

&lt;p&gt;It becomes powerful when it can support real products:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CMS platforms&lt;/li&gt;
&lt;li&gt;Admin systems&lt;/li&gt;
&lt;li&gt;API products&lt;/li&gt;
&lt;li&gt;AI workflows&lt;/li&gt;
&lt;li&gt;Enterprise dashboards&lt;/li&gt;
&lt;li&gt;Developer platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Apex AI, the CMS direction becomes even stronger.&lt;/p&gt;

&lt;p&gt;Content systems are one of the clearest places where AI can help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate drafts&lt;/li&gt;
&lt;li&gt;Improve content&lt;/li&gt;
&lt;li&gt;Critique content&lt;/li&gt;
&lt;li&gt;Translate content&lt;/li&gt;
&lt;li&gt;Classify content&lt;/li&gt;
&lt;li&gt;Extract metadata&lt;/li&gt;
&lt;li&gt;Build summaries&lt;/li&gt;
&lt;li&gt;Power editorial assistants&lt;/li&gt;
&lt;li&gt;Automate workflow tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;MonkeysLegion 2.0 is the foundation for that future.&lt;/p&gt;


&lt;h2&gt;
  
  
  Open Source and Community-Driven
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion is open source.&lt;/p&gt;

&lt;p&gt;The project started from a simple belief:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Modern PHP projects deserve a framework that is fast, cloud-native, AI-ready, and clear to work with.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Version 2.0 is a call to developers, agencies, founders, and engineering teams:&lt;/p&gt;

&lt;p&gt;Come test it.&lt;br&gt;&lt;br&gt;
Break it.&lt;br&gt;&lt;br&gt;
Improve it.&lt;br&gt;&lt;br&gt;
Build with it.&lt;br&gt;&lt;br&gt;
Open issues.&lt;br&gt;&lt;br&gt;
Submit pull requests.&lt;br&gt;&lt;br&gt;
Share ideas.&lt;br&gt;&lt;br&gt;
Help shape the next generation of PHP tooling.&lt;/p&gt;


&lt;h2&gt;
  
  
  Who MonkeysLegion 2.0 Is For
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion 2.0 is for developers and teams who want to build serious software with PHP.&lt;/p&gt;

&lt;p&gt;It is for the solo founder building a SaaS product.&lt;/p&gt;

&lt;p&gt;It is for the agency building multiple client platforms.&lt;/p&gt;

&lt;p&gt;It is for the backend engineer building APIs.&lt;/p&gt;

&lt;p&gt;It is for the enterprise team that needs maintainable internal tools.&lt;/p&gt;

&lt;p&gt;It is for the startup that wants speed without technical debt.&lt;/p&gt;

&lt;p&gt;It is for the developer who loves PHP but wants a framework that feels modern, clean, fast, and ready for AI-powered products.&lt;/p&gt;

&lt;p&gt;It is for builders who want control.&lt;/p&gt;


&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;The easiest way to start is with the official skeleton.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer create-project monkeyscloud/monkeyslegion-skeleton my-app
&lt;span class="nb"&gt;cd &lt;/span&gt;my-app
composer serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, you can begin building APIs, services, dashboards, internal tools, SaaS applications, AI workflows, CMS platforms, or custom products using the MonkeysLegion architecture.&lt;/p&gt;

&lt;p&gt;Useful links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://monkeyslegion.com" rel="noopener noreferrer"&gt;monkeyslegion.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Release article: &lt;a href="https://monkeyslegion.com/news/2026-04/monkeyslegion-2-announcing" rel="noopener noreferrer"&gt;monkeyslegion.com/news/2026-04/monkeyslegion-2-announcing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Framework: &lt;a href="https://github.com/MonkeysCloud/MonkeysLegion" rel="noopener noreferrer"&gt;github.com/MonkeysCloud/MonkeysLegion&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Skeleton: &lt;a href="https://github.com/MonkeysCloud/MonkeysLegion-Skeleton" rel="noopener noreferrer"&gt;github.com/MonkeysCloud/MonkeysLegion-Skeleton&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;MonkeysLegion 2.0 is built with a clear belief:&lt;/p&gt;

&lt;p&gt;PHP is not done.&lt;/p&gt;

&lt;p&gt;PHP is still one of the most important languages on the web. But the ecosystem needs new ideas, new tooling, and new frameworks that match how software is built today.&lt;/p&gt;

&lt;p&gt;MonkeysLegion is our answer.&lt;/p&gt;

&lt;p&gt;A feather-light framework.&lt;br&gt;&lt;br&gt;
A modular ecosystem.&lt;br&gt;&lt;br&gt;
A cloud-native foundation.&lt;br&gt;&lt;br&gt;
A clean architecture for modern PHP.&lt;br&gt;&lt;br&gt;
A faster runtime path.&lt;br&gt;&lt;br&gt;
A developer platform built for speed, clarity, and scale.&lt;br&gt;&lt;br&gt;
An AI orchestration layer for the next generation of PHP applications.&lt;/p&gt;

&lt;p&gt;Version 2.0 is not the finish line.&lt;/p&gt;

&lt;p&gt;It is the foundation.&lt;/p&gt;

&lt;p&gt;Welcome to MonkeysLegion 2.0.&lt;/p&gt;

&lt;p&gt;Welcome to Apex AI.&lt;/p&gt;

&lt;p&gt;Let’s build the next era of PHP.&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>symfony</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
