<?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: Fernando Paladini</title>
    <description>The latest articles on DEV Community by Fernando Paladini (@paladini).</description>
    <link>https://dev.to/paladini</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%2F1065831%2F79b4d650-5838-4481-a62f-8f03f4010512.jpeg</url>
      <title>DEV Community: Fernando Paladini</title>
      <link>https://dev.to/paladini</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/paladini"/>
    <language>en</language>
    <item>
      <title>Build an Evidence-Friendly Bitcoin Timeline with Static HTML and TimelineJS</title>
      <dc:creator>Fernando Paladini</dc:creator>
      <pubDate>Sun, 15 Nov 2026 12:39:44 +0000</pubDate>
      <link>https://dev.to/paladini/build-an-evidence-friendly-bitcoin-timeline-with-static-html-and-timelinejs-30o3</link>
      <guid>https://dev.to/paladini/build-an-evidence-friendly-bitcoin-timeline-with-static-html-and-timelinejs-30o3</guid>
      <description>&lt;h1&gt;
  
  
  Build an Evidence-Friendly Bitcoin Timeline with Static HTML and TimelineJS
&lt;/h1&gt;

&lt;p&gt;A timeline can make a long technical history easier to explore, but a visual slider alone is not enough. If the events are hidden inside a third-party feed or rendered only after JavaScript runs, readers, search engines, and assistive tools have less context to work with.&lt;/p&gt;

&lt;p&gt;This tutorial shows how to build a small static timeline from the open-source &lt;a href="https://github.com/paladini/bitcoin-history" rel="noopener noreferrer"&gt;The History of Bitcoin repository&lt;/a&gt;. The project stores its event data in the repository, keeps a chronological HTML index in the page, and adds metadata for crawlers and answer engines. The result is an interactive timeline without a backend or database.&lt;/p&gt;

&lt;p&gt;The project is educational. It is not investment advice, and historical price references are snapshots rather than predictions.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;You will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the &lt;code&gt;gh-pages&lt;/code&gt; branch.&lt;/li&gt;
&lt;li&gt;Serve the files with Python's built-in HTTP server.&lt;/li&gt;
&lt;li&gt;Inspect the local event data in &lt;code&gt;assets/js/timeline-data.js&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Render it with TimelineJS.&lt;/li&gt;
&lt;li&gt;Verify the page, metadata, and source links locally.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The repository is MIT licensed and currently documents 37 curated events from 2007 through June 30, 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git.&lt;/li&gt;
&lt;li&gt;Python 3.8 or newer with the &lt;code&gt;py&lt;/code&gt; launcher on Windows, or &lt;code&gt;python3&lt;/code&gt; on macOS and Linux.&lt;/li&gt;
&lt;li&gt;A browser.&lt;/li&gt;
&lt;li&gt;Basic HTML and JavaScript familiarity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The published project uses the &lt;code&gt;gh-pages&lt;/code&gt; branch directly. It is a static site, not a package with a versioned release. That means you should treat the branch contents you clone as the source snapshot you are reviewing.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Run the existing timeline
&lt;/h2&gt;

&lt;p&gt;Clone the branch and start a local server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone --branch gh-pages --depth 1 https://github.com/paladini/bitcoin-history.git
&lt;span class="nb"&gt;cd&lt;/span&gt; bitcoin-history
python -m http.server 8058
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows PowerShell, the equivalent command is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="mi"&gt;8058&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://127.0.0.1:8058/" rel="noopener noreferrer"&gt;http://127.0.0.1:8058/&lt;/a&gt;. The expected result is a page titled â€œThe History of Bitcoin: Interactive Timeline (2007-2026)â€ with a TimelineJS navigation area, a chronological event index, and an educational disclaimer.&lt;/p&gt;

&lt;p&gt;The repository's &lt;a href="https://github.com/paladini/bitcoin-history/blob/gh-pages/README.md" rel="noopener noreferrer"&gt;README&lt;/a&gt; documents the same local server path. In a smoke test of the current branch, the page returned HTTP 200 and served the expected HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Understand the data boundary
&lt;/h2&gt;

&lt;p&gt;The important design choice is that the timeline data lives in the repository instead of a live spreadsheet. Open &lt;a href="https://github.com/paladini/bitcoin-history/blob/gh-pages/assets/js/timeline-data.js" rel="noopener noreferrer"&gt;&lt;code&gt;assets/js/timeline-data.js&lt;/code&gt;&lt;/a&gt;. It assigns a TimelineJS-compatible object to &lt;code&gt;window.bitcoinHistoryTimelineData&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A simplified event has this shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"start_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"2008"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"month"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"day"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"31"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"headline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"The Bitcoin white paper is published"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Satoshi Nakamoto publishes the original peer-to-peer electronic cash proposal."&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;The actual repository keeps the dates and text as strings because that is the structure expected by TimelineJS. It also stores the title and the complete event array in the same file, so a reviewer can inspect the source without opening a database or calling an application API.&lt;/p&gt;

&lt;p&gt;To add an event, append another object to the &lt;code&gt;events&lt;/code&gt; array. Keep the date explicit, use a factual headline, and include a source link in the visible HTML index when the claim depends on a primary document.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Connect the data to TimelineJS
&lt;/h2&gt;

&lt;p&gt;The end of &lt;a href="https://github.com/paladini/bitcoin-history/blob/gh-pages/index.html" rel="noopener noreferrer"&gt;&lt;code&gt;index.html&lt;/code&gt;&lt;/a&gt; loads TimelineJS and then passes the local object to the constructor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.knightlab.com/libs/timeline3/latest/js/timeline.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"assets/js/timeline-data.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="no"&gt;TL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Timeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"timeline-embed"&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="n"&gt;bitcoinHistoryTimelineData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;hash_bookmark&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="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timenav_position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"bottom"&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="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The order matters. The data file must load before the constructor runs. The &lt;code&gt;timeline-embed&lt;/code&gt; element must also exist in the document:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"history"&lt;/span&gt; &lt;span class="na"&gt;aria-label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Interactive Bitcoin timeline"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"timeline-embed"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;hash_bookmark&lt;/code&gt; option lets the selected event be represented in the URL. That makes a particular point in the timeline easier to share and revisit.&lt;/p&gt;

&lt;p&gt;The project currently loads TimelineJS from Knight Lab's CDN. The event data is local, but the page is not fully self-contained offline because the library and stylesheet are external dependencies. If you need an offline archive, download and serve those assets locally, then verify their licenses and update the HTML references.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Keep a crawlable fallback
&lt;/h2&gt;

&lt;p&gt;The interactive component is useful for exploration, but the page does not depend on it for all meaning. The current HTML also contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A visible summary of the covered period.&lt;/li&gt;
&lt;li&gt;An ordered list of chronological milestones.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;time&lt;/code&gt; elements with machine-readable dates.&lt;/li&gt;
&lt;li&gt;Source links for selected historical and regulatory claims.&lt;/li&gt;
&lt;li&gt;A disclaimer that separates education from investment advice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This fallback is valuable when JavaScript fails, when a crawler reads the raw HTML, or when a reader wants to scan the chronology without operating a visual control.&lt;/p&gt;

&lt;p&gt;A simple event entry looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;time&lt;/span&gt; &lt;span class="na"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"2024-01-10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;January 10, 2024&lt;span class="nt"&gt;&amp;lt;/time&amp;gt;&lt;/span&gt;
  - The SEC approves spot Bitcoin exchange-traded products.
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://www.sec.gov/newsroom/speeches-statements/gensler-statement-spot-bitcoin-011023"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Source
  &lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The visible list should stay synchronized with the JavaScript data. A practical workflow is to update the data object first, then add the same event to the HTML index, then run a local review.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Add metadata without hiding the content
&lt;/h2&gt;

&lt;p&gt;The current page uses ordinary HTML metadata rather than relying only on the interactive widget. It includes a canonical URL, a description, Open Graph fields, a Twitter card, Dublin Core fields, and JSON-LD.&lt;/p&gt;

&lt;p&gt;The JSON-LD describes the page as a web page, article, image, breadcrumb, and item list. The repository also publishes &lt;a href="https://github.com/paladini/bitcoin-history/blob/gh-pages/robots.txt" rel="noopener noreferrer"&gt;&lt;code&gt;robots.txt&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://github.com/paladini/bitcoin-history/blob/gh-pages/sitemap.xml" rel="noopener noreferrer"&gt;&lt;code&gt;sitemap.xml&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://github.com/paladini/bitcoin-history/blob/gh-pages/llms.txt" rel="noopener noreferrer"&gt;&lt;code&gt;llms.txt&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These files do not replace good content. They make the page's identity, language, date range, license, and update date easier for different consumers to interpret. Use descriptive titles and headings, keep the canonical URL stable, and ensure that metadata matches the visible page.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Verify changes reproducibly
&lt;/h2&gt;

&lt;p&gt;After editing an event, repeat the same checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python -m http.server 8058
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The page returns HTTP 200.&lt;/li&gt;
&lt;li&gt;The browser shows the timeline and the chronological index.&lt;/li&gt;
&lt;li&gt;The selected event updates the URL hash.&lt;/li&gt;
&lt;li&gt;The event appears in both &lt;code&gt;timeline-data.js&lt;/code&gt; and the HTML list.&lt;/li&gt;
&lt;li&gt;Source links open the intended primary pages.&lt;/li&gt;
&lt;li&gt;The disclaimer remains visible.&lt;/li&gt;
&lt;li&gt;No API key, wallet credential, or private data was added.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a command-line smoke test, request the local page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl -I http://127.0.0.1:8058/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful response should include &lt;code&gt;HTTP/1.0 200 OK&lt;/code&gt; or an equivalent HTTP 200 status from the local Python server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this structure works
&lt;/h2&gt;

&lt;p&gt;The repository separates three concerns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;timeline-data.js&lt;/code&gt; is the structured event source for the visualization.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;index.html&lt;/code&gt; provides the readable fallback and metadata.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;robots.txt&lt;/code&gt;, &lt;code&gt;sitemap.xml&lt;/code&gt;, and &lt;code&gt;llms.txt&lt;/code&gt; describe how the published page can be discovered.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That separation is small enough to review in a pull request. It also avoids making the interactive library responsible for facts, source attribution, or the page's entire information architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure modes and limitations
&lt;/h2&gt;

&lt;p&gt;If the timeline is blank, check that the external TimelineJS script loaded, that &lt;code&gt;timeline-data.js&lt;/code&gt; appears before the constructor, and that the &lt;code&gt;timeline-embed&lt;/code&gt; element exists.&lt;/p&gt;

&lt;p&gt;If the page works locally but not on a host, check the base path, case-sensitive asset names, and HTTPS mixed-content rules. GitHub Pages serves the project under &lt;code&gt;/bitcoin-history/&lt;/code&gt;, so relative asset paths are safer than root-relative paths.&lt;/p&gt;

&lt;p&gt;The project is a curated historical view, not a complete ledger of every Bitcoin event. Some entries include price snapshots, which can become stale. Regulatory and institutional claims can also require updates as official sources change. The MIT license permits reuse, but reused copies should preserve attribution and should not imply that the original author endorses financial decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does this need a database?
&lt;/h3&gt;

&lt;p&gt;No. The current project keeps the event data in a JavaScript file and publishes static HTML.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it fully offline?
&lt;/h3&gt;

&lt;p&gt;Not as published. The event data is local, but TimelineJS is loaded from a CDN. Self-host the library and stylesheet if offline operation is a requirement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is this an investment tool?
&lt;/h3&gt;

&lt;p&gt;No. It is an educational chronology. It does not predict prices, recommend assets, or manage wallets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I add my own milestones?
&lt;/h3&gt;

&lt;p&gt;Yes. Add a dated object to the event array and mirror it in the HTML index. Cite a primary source for claims that readers may want to verify.&lt;/p&gt;

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

&lt;p&gt;A useful historical visualization does not have to begin with a database. A local event file, a readable HTML fallback, explicit sources, and a small client-side renderer are enough to build something interactive and reviewable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI assistance disclosure:&lt;/strong&gt; AI assistance was used to organize this tutorial and improve its wording. The repository behavior, file names, current branch, license, and smoke-test results were checked against the linked primary sources.&lt;/p&gt;

&lt;p&gt;What would you add first: more historical events, local self-hosted TimelineJS assets, or automated checks that compare the HTML index with the JavaScript event data?&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>github</category>
      <category>tutorial</category>
      <category>bitcoin</category>
    </item>
    <item>
      <title>Build a Reproducible POSCOMP Study Library from an Open Source Archive</title>
      <dc:creator>Fernando Paladini</dc:creator>
      <pubDate>Sat, 14 Nov 2026 12:40:37 +0000</pubDate>
      <link>https://dev.to/paladini/build-a-reproducible-poscomp-study-library-from-an-open-source-archive-1ebe</link>
      <guid>https://dev.to/paladini/build-a-reproducible-poscomp-study-library-from-an-open-source-archive-1ebe</guid>
      <description>&lt;p&gt;Preparing for a large exam often starts with a familiar problem: the material exists, but it is scattered across old links, downloaded files, and personal notes. That makes it difficult to answer a simple question: which exams and answer keys do I actually have, and can someone else reproduce my setup?&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/paladini/awesome-poscomp" rel="noopener noreferrer"&gt;awesome-poscomp repository&lt;/a&gt; solves the archive part of that problem. It collects POSCOMP exam papers and answer keys in one public Git repository, with a year-by-year index. POSCOMP is the Brazilian national exam commonly used in admission processes for graduate computing programs. The &lt;a href="https://www.sbc.org.br/educacao/poscomp" rel="noopener noreferrer"&gt;official SBC POSCOMP page&lt;/a&gt; is the authority for the exam itself; the repository is a convenient, reviewable study archive.&lt;/p&gt;

&lt;p&gt;This tutorial shows how to create a local study library, verify that the repository is the one you intended to use, and inspect the available years without relying on an opaque download folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Clone &lt;code&gt;paladini/awesome-poscomp&lt;/code&gt;, inspect its README and license, list the local PDF files, and compare the result with the repository index. Keep the repository commit with your notes so the study setup can be reproduced later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git 2.x installed and available on your PATH.&lt;/li&gt;
&lt;li&gt;A PDF viewer. A command-line PDF text tool is optional.&lt;/li&gt;
&lt;li&gt;A Windows PowerShell, macOS, or Linux shell.&lt;/li&gt;
&lt;li&gt;Enough disk space for the archive and any notes you add.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repository is public and uses the MIT license. It is an archive of study material, not an official registration system, score calculator, or guarantee that a particular graduate program accepts a result. Check the official SBC material and the rules of the program you are applying to.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Clone a known revision
&lt;/h2&gt;

&lt;p&gt;Start with a dedicated directory. The URL below is the public repository URL, so the command is independently checkable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/paladini/awesome-poscomp.git
&lt;span class="nb"&gt;cd&lt;/span&gt; awesome-poscomp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Record the revision used for your study plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rev-parse --short HEAD
git remote -v
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current default branch is &lt;code&gt;master&lt;/code&gt;. A commit identifier matters because an archive can gain a new exam or fix a link later. If you share a study checklist with another person, include the commit hash beside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Read the index before opening PDFs
&lt;/h2&gt;

&lt;p&gt;The repository README is the catalog. It groups the collection by year and links to a question booklet and an answer key when both are available. It currently describes material from 2002 through 2024 and includes a provisional 2025 entry.&lt;/p&gt;

&lt;p&gt;Open the README in a browser or inspect it from the shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git show HEAD:README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The index is useful for two reasons. First, it tells you whether a local file is expected for a particular year. Second, it distinguishes a repository-hosted PDF from a link to an external SBC, university, or publication host. That distinction matters when a link stops working: the repository can preserve some files locally, but it cannot guarantee the availability of an external website.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Build a local inventory
&lt;/h2&gt;

&lt;p&gt;On macOS or Linux, list the PDFs with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find poscomp -type f -iname &lt;span class="s1"&gt;'*.pdf'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; sort
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On PowerShell, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nc"&gt;Get&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;ChildItem&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;\&lt;span class="n"&gt;poscomp&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;Recurse&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;File&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;Filter&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nc"&gt;Sort&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="nc"&gt;FullName&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="nc"&gt;Select&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;ExpandProperty&lt;/span&gt; &lt;span class="nc"&gt;FullName&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output gives you a concrete inventory instead of a vague claim that the archive is complete. You can save it as a review artifact without changing the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find poscomp -type f -iname &lt;span class="s1"&gt;'*.pdf'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; sort &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; poscomp-pdfs.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file names should make the role of each document clear, such as a question booklet or a gabarito. Do not assume that every year has the same local layout. The README is the source for what each link means.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Check that the PDFs are readable
&lt;/h2&gt;

&lt;p&gt;Open one question booklet and its answer key for a recent year, then repeat the check for an older year. This catches incomplete downloads and viewer problems early.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;pdfinfo&lt;/code&gt; is installed, you can inspect the document metadata:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pdfinfo poscomp/2024/poscomp_caderno_prova_2024.pdf
pdfinfo poscomp/2024/poscomp_prova_gabarito_2024.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact file names may change as the archive evolves, so use the paths returned by your inventory command. A successful metadata read is only a basic integrity check. It does not prove that every answer is correct or that a PDF is an official publication.&lt;/p&gt;

&lt;p&gt;For a stronger local check, extract a few pages to text when your PDF tools support it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pdftotext -f 1 -l 2 poscomp/2024/poscomp_caderno_prova_2024.pdf -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for a title, year, or question heading. This is a reproducible sanity check, not a replacement for reading the source document.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Turn the archive into a study loop
&lt;/h2&gt;

&lt;p&gt;A repository becomes useful when it supports a repeatable decision process. Pick one year and one subject area from the program topics listed in the README, then work through the question booklet without consulting the key. Save your answers outside the cloned repository or on a separate branch.&lt;/p&gt;

&lt;p&gt;After the attempt, compare with the answer key and record only the questions that need review. The README groups the content into areas such as mathematics, foundations of computing, and computing technology. That makes it possible to build a small backlog instead of rereading every subject after every practice session.&lt;/p&gt;

&lt;p&gt;For example, a simple weekly routine can be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select one available year from the index.&lt;/li&gt;
&lt;li&gt;Complete a bounded set of questions under a fixed time limit.&lt;/li&gt;
&lt;li&gt;Compare answers with the matching key.&lt;/li&gt;
&lt;li&gt;Tag missed questions by topic.&lt;/li&gt;
&lt;li&gt;Repeat the same topic with another year.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The important detail is the evidence trail. Keep the repository commit, year, PDF path, and your notes together. If a link is later corrected, you can tell which source you used at the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Expected result
&lt;/h2&gt;

&lt;p&gt;At the end, you should have a local checkout with an identifiable Git revision, a list of available PDFs, and a study note that points to exact files. A minimal verification report might contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Repository: paladini/awesome-poscomp
Revision: &amp;lt;output of git rev-parse --short HEAD&amp;gt;
Archive index: README.md
Recent sample: poscomp/2024/
Older sample: &amp;lt;path selected from the local inventory&amp;gt;
Integrity check: PDF metadata and first-page text read successfully
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This result is more useful than a folder named &lt;code&gt;poscomp-final&lt;/code&gt;, because another person can reproduce the same starting point and see which parts came from the public archive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Git workflow helps
&lt;/h2&gt;

&lt;p&gt;Git provides three practical properties for study material. The remote URL identifies the source. The commit hash identifies the exact snapshot. A text inventory identifies the files you actually used. Together, those properties reduce accidental mixing of answer keys from different years or silently missing documents.&lt;/p&gt;

&lt;p&gt;The workflow also keeps the boundary between community-maintained convenience and official information visible. The repository links to the SBC and other public sources, but it does not replace them. Always verify current exam dates, registration rules, and program requirements with the responsible institution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure modes and limitations
&lt;/h2&gt;

&lt;p&gt;If &lt;code&gt;git clone&lt;/code&gt; fails, check network access and the repository URL. If a PDF is missing, compare your local tree with the README because some entries point to external sites rather than repository files. If a PDF opens but has no selectable text, use a viewer or OCR tool carefully and treat extracted text as a convenience layer.&lt;/p&gt;

&lt;p&gt;The archive does not automatically grade answers, generate a personalized curriculum, or certify that a question set is current. It also cannot guarantee that external links remain available. The repository license permits reuse under its terms, but the individual exam documents can have their own provenance and publication context. Preserve source links and attribution when redistributing material.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is this the official POSCOMP website?
&lt;/h3&gt;

&lt;p&gt;No. The &lt;a href="https://www.sbc.org.br/educacao/poscomp" rel="noopener noreferrer"&gt;SBC POSCOMP page&lt;/a&gt; is the official starting point for exam information. This repository is a public study archive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to install a programming language?
&lt;/h3&gt;

&lt;p&gt;No. Git and a PDF viewer are enough for the basic workflow. PDF command-line tools are optional verification helpers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I edit the cloned repository with my answers?
&lt;/h3&gt;

&lt;p&gt;Keep personal answers and annotations separate unless you intentionally want to create a reviewable fork. A separate notes directory makes it easier to update the archive without merging personal files into source material.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I rely on the newest year shown in the README?
&lt;/h3&gt;

&lt;p&gt;Treat each entry according to its label. A provisional or external link needs separate confirmation. Check the official SBC publication before treating a document as authoritative for a current exam.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;awesome-poscomp&lt;/code&gt; turns a scattered set of POSCOMP references into a reviewable Git starting point. Clone it, record the revision, inventory the PDFs, verify representative files, and keep your study notes tied to exact paths. That small amount of provenance makes a personal preparation workflow easier to reproduce and safer to update.&lt;/p&gt;

&lt;p&gt;Have you found a useful way to turn past POSCOMP questions into a topic-based review backlog without losing the source year and answer-key provenance?&lt;/p&gt;

&lt;p&gt;Disclosure: AI assistance was used to organize and edit this tutorial. The repository links, commands, and limitations were checked against the public project and official POSCOMP source pages.&lt;/p&gt;

</description>
      <category>education</category>
      <category>github</category>
      <category>tutorial</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Install a Local MCP Utility Plugin in Cursor with DevUtils</title>
      <dc:creator>Fernando Paladini</dc:creator>
      <pubDate>Fri, 13 Nov 2026 12:36:43 +0000</pubDate>
      <link>https://dev.to/paladini/install-a-local-mcp-utility-plugin-in-cursor-with-devutils-gjb</link>
      <guid>https://dev.to/paladini/install-a-local-mcp-utility-plugin-in-cursor-with-devutils-gjb</guid>
      <description>&lt;p&gt;AI assistants are useful at explaining code, but they should not improvise a SHA-256 digest, decode a JWT by hand, or guess whether an IP address belongs to a subnet. Those are small, deterministic operations that deserve a tool with a clear contract.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/paladini/devutils-cursor-plugin" rel="noopener noreferrer"&gt;DevUtils MCP Plugin&lt;/a&gt; packages that idea for Cursor and Claude Code. It installs a local MCP server with utilities for hashing, encoding, UUID generation, JWT inspection, JSON, network calculations, and text processing. The plugin wrapper is open source under the MIT license, and its configuration starts the server through &lt;code&gt;npx&lt;/code&gt; on your own machine.&lt;/p&gt;

&lt;p&gt;This tutorial installs the plugin, shows the equivalent configuration, verifies the MCP stdio handshake, and explains the boundary between local processing and package downloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Install &lt;strong&gt;DevUtils MCP&lt;/strong&gt; from Cursor Settings, enable the &lt;code&gt;devutils&lt;/code&gt; server, and ask your assistant to use a deterministic utility such as UUID generation or JSON validation. The plugin manifest is version 1.0.7 on the repository's current branch. The wrapped &lt;code&gt;devutils-mcp-server&lt;/code&gt; package requires Node.js 18 or newer.&lt;/p&gt;

&lt;p&gt;The server exposes 36 tools. They run locally after the package is available, so the inputs you send to a tool are handled by the local MCP process rather than an API operated by the plugin author.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cursor with MCP support, or Claude Code with plugin support.&lt;/li&gt;
&lt;li&gt;Node.js 18 or newer. The server README recommends Node.js 22.&lt;/li&gt;
&lt;li&gt;Permission to let &lt;code&gt;npx&lt;/code&gt; download &lt;code&gt;devutils-mcp-server&lt;/code&gt; the first time it runs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The plugin repository is &lt;a href="https://github.com/paladini/devutils-cursor-plugin" rel="noopener noreferrer"&gt;paladini/devutils-cursor-plugin&lt;/a&gt;. It is a wrapper and distribution package. The implementation of the 36 utilities lives in the related &lt;a href="https://github.com/paladini/devutils-mcp-server" rel="noopener noreferrer"&gt;devutils-mcp-server repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install it in Cursor
&lt;/h2&gt;

&lt;p&gt;The repository documents this path for Cursor:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Cursor Settings&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Open &lt;strong&gt;Customize&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Search for &lt;strong&gt;DevUtils MCP&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Install&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Enable the &lt;code&gt;devutils&lt;/code&gt; MCP server under &lt;strong&gt;Customize &amp;gt; MCPs&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can also choose &lt;strong&gt;Add from GitHub&lt;/strong&gt; and enter &lt;code&gt;paladini/devutils-cursor-plugin&lt;/code&gt;. The repository's Cursor manifest names the plugin &lt;code&gt;devutils-mcp&lt;/code&gt;, declares version 1.0.7, and points its MCP configuration at the &lt;code&gt;devutils&lt;/code&gt; server.&lt;/p&gt;

&lt;p&gt;After enabling it, start with a request that has an observable result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use the DevUtils MCP server to generate one UUID v4 and return only the UUID.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important detail is the request's explicit tool choice. It gives you a way to distinguish an MCP result from an answer the model generated without calling the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Cursor is launching
&lt;/h2&gt;

&lt;p&gt;The plugin contains this small stdio configuration:&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="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;"devutils"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"devutils-mcp-server"&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;&lt;code&gt;npx -y&lt;/code&gt; can download the package when it is not already available. Once started, the MCP server communicates over standard input and standard output. The plugin does not add a web endpoint, API key, or remote service.&lt;/p&gt;

&lt;p&gt;If your client does not support the plugin marketplace, the server README documents the same command for manual MCP configuration. For example, a client that uses an &lt;code&gt;mcpServers&lt;/code&gt; object can reuse the JSON above. Keep the command and arguments unchanged unless you have a deliberate reason to pin or replace the package source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the connection without guessing
&lt;/h2&gt;

&lt;p&gt;You can test the server boundary outside Cursor with a JSON-RPC initialize message. This is a protocol smoke test, not a test of every utility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke-test","version":"1.0"}}}'&lt;/span&gt;
&lt;span class="nv"&gt;$message&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;npx&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="n"&gt;devutils&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A successful response contains a JSON-RPC result with server information and a &lt;code&gt;tools&lt;/code&gt; capability. The server also writes a short startup line to stderr. Seeing the response confirms that Node.js can resolve the package and that the process accepts MCP input over stdio.&lt;/p&gt;

&lt;p&gt;For a client-level verification, ask Cursor to perform a simple operation and then repeat it with a known local value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use DevUtils to calculate the SHA-256 hash of the exact string: hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result should be deterministic. You can compare it with a local command such as &lt;code&gt;Get-FileHash&lt;/code&gt; for a file, or with a trusted library for a string. The point is not that MCP is faster than a native library. The point is that an assistant can call a named operation with a defined input instead of inventing a result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful operations to try
&lt;/h2&gt;

&lt;p&gt;The server README groups the tools into eight areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hashing: MD5, SHA-1, SHA-256, SHA-512, bcrypt generation, and bcrypt verification.&lt;/li&gt;
&lt;li&gt;Encoding: Base64, URL, HTML entity, and hexadecimal conversions.&lt;/li&gt;
&lt;li&gt;Generators: UUID v4, Nano ID, passwords, and random hex values.&lt;/li&gt;
&lt;li&gt;JWT: decoding and structure or expiration validation.&lt;/li&gt;
&lt;li&gt;JSON: formatting, validation, and dot-notation queries.&lt;/li&gt;
&lt;li&gt;Converters: timestamps, number bases, colors, and byte units.&lt;/li&gt;
&lt;li&gt;Network: CIDR calculations and IPv4 or IPv6 classification.&lt;/li&gt;
&lt;li&gt;Text: case conversion, slugs, regular-expression checks, statistics, and diffs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are good assistant tasks because each one has a compact input and an inspectable output. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use DevUtils to validate this JSON and explain the error location: {"enabled": true,}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use DevUtils to calculate the network and host range for 192.168.10.0/24.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not send a real password, production token, customer record, or private document just to test the integration. Use synthetic values first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the local boundary matters
&lt;/h2&gt;

&lt;p&gt;The plugin's &lt;a href="https://github.com/paladini/devutils-cursor-plugin/blob/master/PRIVACY.md" rel="noopener noreferrer"&gt;privacy policy&lt;/a&gt; says the tools process inputs on the local machine and that the plugin author does not collect or transmit them. That is a property of this plugin and server design, not a general guarantee about every MCP client or every package installed through &lt;code&gt;npx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There is still a network step: &lt;code&gt;npx&lt;/code&gt; may fetch the npm package. The repository documents npm as a third-party service involved in that download. After the package is available, the server is intended to run locally and its README states that the utilities use no external APIs.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/paladini/devutils-cursor-plugin/blob/master/SECURITY.md" rel="noopener noreferrer"&gt;security policy&lt;/a&gt; also makes the scope clear: the wrapper adds no network endpoints, while input validation and dependency updates belong to the server implementation. Review the server source and package provenance before using it with sensitive workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure modes and limits
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The server does not appear in Cursor
&lt;/h3&gt;

&lt;p&gt;Check that the plugin is installed and that &lt;code&gt;devutils&lt;/code&gt; is enabled under the MCP settings. Then verify &lt;code&gt;node -v&lt;/code&gt; in a terminal. A missing Node.js installation prevents the &lt;code&gt;npx&lt;/code&gt; command from starting.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;npx&lt;/code&gt; cannot download the package
&lt;/h3&gt;

&lt;p&gt;This is usually a registry, proxy, permission, or network problem. Run &lt;code&gt;npx -y devutils-mcp-server&lt;/code&gt; directly and inspect the error. Do not treat a successful plugin installation as proof that the runtime package is available.&lt;/p&gt;

&lt;h3&gt;
  
  
  The assistant answers without using the tool
&lt;/h3&gt;

&lt;p&gt;Ask it to name the DevUtils operation explicitly, then look for an MCP tool call in the client transcript. A plausible-looking answer is not verification.&lt;/p&gt;

&lt;h3&gt;
  
  
  You need application-level performance
&lt;/h3&gt;

&lt;p&gt;Use a native library directly inside your application. The project itself says DevUtils is intended for AI-agent workflows, not as a replacement for optimized application code. MCP adds a process and protocol boundary.&lt;/p&gt;

&lt;h3&gt;
  
  
  You need a security guarantee
&lt;/h3&gt;

&lt;p&gt;Do not infer one from the word “local.” Local software can still have bugs, compromised dependencies, unsafe inputs, or an overly privileged client. Pin dependencies where your deployment policy requires it, review the package source, and keep secrets out of exploratory prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does the plugin require an API key?
&lt;/h3&gt;

&lt;p&gt;No. The documented configuration starts the local server with &lt;code&gt;npx&lt;/code&gt;, and the repository describes zero external API calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use it without Cursor?
&lt;/h3&gt;

&lt;p&gt;Yes. The server can be configured directly in other MCP-compatible clients. Claude Code can install the plugin with &lt;code&gt;/plugin marketplace add paladini/devutils-cursor-plugin&lt;/code&gt; followed by &lt;code&gt;/plugin install devutils-mcp@devutils-cursor-plugin&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the plugin the same as the server?
&lt;/h3&gt;

&lt;p&gt;No. The plugin repository supplies manifests and client installation metadata. The related server repository contains the MCP implementation and utility tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are JWTs sent to a cloud service?
&lt;/h3&gt;

&lt;p&gt;The plugin documentation says JWT processing happens locally. Even so, use test tokens while evaluating the setup and verify the behavior of the exact client and package version you deploy.&lt;/p&gt;

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

&lt;p&gt;DevUtils is a small, practical way to give an MCP-capable assistant deterministic developer utilities without adding a custom API service. Install the plugin, verify the stdio handshake, and start with synthetic inputs. The useful security habit is to understand both boundaries: package acquisition may use npm, while the tool operations are designed to execute locally.&lt;/p&gt;

&lt;p&gt;Have you found a developer task where a deterministic MCP utility prevents more errors than a general-purpose coding assistant alone?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI assistance disclosure: I used AI assistance to organize and edit this tutorial. The commands, versions, configuration, project metadata, and security notes were checked against the linked primary sources and a local MCP initialize smoke test.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>cursor</category>
      <category>tutorial</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Give Your Coding Agent Semantic Code Navigation with Locus MCP</title>
      <dc:creator>Fernando Paladini</dc:creator>
      <pubDate>Thu, 12 Nov 2026 12:42:03 +0000</pubDate>
      <link>https://dev.to/paladini/give-your-coding-agent-semantic-code-navigation-with-locus-mcp-5a4j</link>
      <guid>https://dev.to/paladini/give-your-coding-agent-semantic-code-navigation-with-locus-mcp-5a4j</guid>
      <description>&lt;p&gt;Text search is useful until an AI coding agent has to answer a structural question: where is this symbol really defined, which callers will a refactor affect, or what type does this expression have? A grep result can show matching text, but it cannot reliably distinguish a definition from a re-export, a comment, or an unrelated variable with the same name.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/paladini/locus-mcp" rel="noopener noreferrer"&gt;Locus MCP&lt;/a&gt; adds a small semantic layer to that workflow. It connects an MCP-compatible agent to language servers, so the agent can ask for definitions, references, hover information, diagnostics, status, and a rename preview through six focused tools.&lt;/p&gt;

&lt;p&gt;This tutorial sets up Locus for a TypeScript project, checks the language-server boundary, and adds an MCP configuration that an agent host can start automatically. The goal is not to replace an IDE or make refactors autonomous. The goal is to give an agent better evidence before it edits code.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Install a language server, run Locus initialization and checks, then add the server to your agent configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm install -g typescript-language-server typescript
npx -y @paladini/locus-mcp init
npx -y @paladini/locus-mcp check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current npm package is &lt;code&gt;@paladini/locus-mcp@0.1.5&lt;/code&gt; and requires Node.js 22 or newer. Pin that version in repeatable setup scripts when you do not want a moving &lt;code&gt;latest&lt;/code&gt; tag.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 22 or newer.&lt;/li&gt;
&lt;li&gt;An MCP-compatible host such as Cursor, Codex, or Claude Code.&lt;/li&gt;
&lt;li&gt;A project containing source files that a supported language server can analyze.&lt;/li&gt;
&lt;li&gt;The relevant language server on your &lt;code&gt;PATH&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For TypeScript and JavaScript, the repository documents &lt;code&gt;typescript-language-server&lt;/code&gt; and &lt;code&gt;typescript&lt;/code&gt;. Python, Go, and Rust use separate language servers described in the &lt;a href="https://github.com/paladini/locus-mcp/blob/main/docs/getting-started.md" rel="noopener noreferrer"&gt;Locus getting started guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Locus itself does not bundle those language servers. That separation matters: Locus provides the MCP and LSP client layer, while the language server remains an independently installed tool with its own version and configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Install and initialize Locus
&lt;/h2&gt;

&lt;p&gt;Install the TypeScript language server globally, then initialize the project from its root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm install -g typescript-language-server typescript
npx -y @paladini/locus-mcp init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The documented &lt;code&gt;init&lt;/code&gt; command generates &lt;code&gt;locus.toml&lt;/code&gt; and &lt;code&gt;locus.json&lt;/code&gt; without replacing existing configuration files. Keep the generated files under version control if the project team wants a shared language-server setup. Use a project-scoped configuration when different repositories need different server choices.&lt;/p&gt;

&lt;p&gt;Now check whether the required binaries are visible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx -y @paladini/locus-mcp check
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The check is intentionally honest. It reports available and missing binaries instead of pretending that a language can be analyzed. A missing server is a setup problem, not an LLM failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Verify the local boundary
&lt;/h2&gt;

&lt;p&gt;Before connecting an agent, inspect the generated files. A minimal configuration can look like this:&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="nl"&gt;"root"&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="nl"&gt;"servers"&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="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"typescript"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"typescript-language-server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"--stdio"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;"extensions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;".ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;".tsx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;".js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;".jsx"&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;The exact schema and built-in defaults are maintained in the &lt;a href="https://github.com/paladini/locus-mcp/blob/main/docs/configuration.md" rel="noopener noreferrer"&gt;Locus configuration reference&lt;/a&gt;. When the server list is empty, Locus uses built-in defaults. Custom entries are useful when a project needs a non-default binary, additional arguments, or a narrower extension mapping.&lt;/p&gt;

&lt;p&gt;The package also exposes a &lt;code&gt;status&lt;/code&gt; MCP tool. It reports language-server readiness and missing binaries, while &lt;code&gt;diagnostics&lt;/code&gt; can report file or workspace problems. These results should be treated as evidence with a scope and a timestamp, not as a guarantee that the whole project is correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Add Locus to your agent host
&lt;/h2&gt;

&lt;p&gt;Locus is normally started by the host over stdio. You do not need to run &lt;code&gt;serve&lt;/code&gt; manually for the normal MCP configuration.&lt;/p&gt;

&lt;p&gt;For Cursor, create or edit &lt;code&gt;.cursor/mcp.json&lt;/code&gt; in the project:&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="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;"locus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"@paladini/locus-mcp@0.1.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"serve"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"/absolute/path/to/your/project"&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;For Codex, the repository documents the equivalent TOML configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="k"&gt;[mcp_servers.locus]&lt;/span&gt;
&lt;span class="na"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"npx"&lt;/span&gt;
&lt;span class="na"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"@paladini/locus-mcp@0.1.5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"serve"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;cwd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/absolute/path/to/your/project"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the host's normal reload or restart action after saving the configuration. Then ask the agent to locate a symbol and list its references. A useful first request is: "Use Locus to find the definition of UserService and list its references."&lt;/p&gt;

&lt;h2&gt;
  
  
  What the six tools change
&lt;/h2&gt;

&lt;p&gt;The value is not the number six. It is the division of labor between the agent and the language server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;locate&lt;/code&gt; finds a symbol definition or lists symbols in a file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;refs&lt;/code&gt; finds references or implementations at a position.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hover&lt;/code&gt; returns type information and documentation.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;diagnostics&lt;/code&gt; reports file or workspace diagnostics.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;status&lt;/code&gt; shows server readiness and binary availability.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rename&lt;/code&gt; previews a rename and can return an apply plan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last tool is worth treating carefully. A rename preview is not the same as an approved write. Locus can prepare a plan, but your agent still decides whether and how to apply edits. This keeps the tool focused on code intelligence instead of turning it into an autonomous refactoring system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproducible verification
&lt;/h2&gt;

&lt;p&gt;You can verify the installation without asking an agent to edit your repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx -y @paladini/locus-mcp init
npx -y @paladini/locus-mcp check
npx -y @paladini/locus-mcp warm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;init&lt;/code&gt; is idempotent according to the repository's CLI tests, so running it again should preserve existing configuration. &lt;code&gt;warm&lt;/code&gt; starts configured language servers early, which can make the first semantic request less surprising on a larger project. If a binary is missing, &lt;code&gt;check&lt;/code&gt; can return a non-zero status. Install the missing language server and run the check again.&lt;/p&gt;

&lt;p&gt;For a source-level verification of the project itself, the repository's current main checkout passed &lt;code&gt;npm run build&lt;/code&gt;, &lt;code&gt;npm run typecheck&lt;/code&gt;, and &lt;code&gt;npm test&lt;/code&gt;. The test suite reported 94 passing tests and one skipped integration case because &lt;code&gt;pyright-langserver&lt;/code&gt; was not installed in that environment. The same suite verified the TypeScript language-server integration and the six-tool MCP registration. These are repository checks, not a promise that every host or language configuration will behave identically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure modes and limitations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The agent sees no symbols
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;locus check&lt;/code&gt; first. If the language server is missing, not executable, or mapped to the wrong file extension, semantic requests cannot succeed. Also confirm that the host passes the intended project directory through &lt;code&gt;cwd&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The server is still starting
&lt;/h3&gt;

&lt;p&gt;Language servers may need time to index a workspace. Locus exposes a &lt;code&gt;server_starting&lt;/code&gt; result so the client can retry rather than treating a temporary startup state as a definitive absence of results. &lt;code&gt;warm&lt;/code&gt; can move that startup earlier in the session, but it does not eliminate indexing cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Text search is still necessary
&lt;/h3&gt;

&lt;p&gt;Locus does not replace grep. The repository explicitly positions ordinary text search as the right tool for comments, configuration keys, log lines, and other non-symbol content. Use semantic tools for code structure and text search for literal content.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security and trust boundaries
&lt;/h3&gt;

&lt;p&gt;Locus reads project files through language-server processes and communicates with the agent host over stdio. It does not provide a security guarantee for a language server, an editor host, or a third-party plugin. Keep language servers and agent hosts updated, review MCP configuration before enabling it, and avoid granting a tool more filesystem scope than the project requires.&lt;/p&gt;

&lt;p&gt;The project is MIT licensed, but dependencies and language servers have their own licenses and security posture. Read the repository's &lt;a href="https://github.com/paladini/locus-mcp/blob/main/SECURITY.md" rel="noopener noreferrer"&gt;security policy&lt;/a&gt; and the &lt;a href="https://github.com/paladini/locus-mcp/blob/main/LICENSE" rel="noopener noreferrer"&gt;MIT license&lt;/a&gt; before redistributing or deploying it in a managed environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does Locus edit code?
&lt;/h3&gt;

&lt;p&gt;No. Its tools provide navigation, diagnostics, status, and rename previews. Your agent's normal edit mechanism remains responsible for changing files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which editors are supported?
&lt;/h3&gt;

&lt;p&gt;The documented setup includes Cursor, Codex, and Claude Code. The deeper requirement is an MCP-compatible host that can start a stdio server with a project working directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need every language server?
&lt;/h3&gt;

&lt;p&gt;No. Install only the servers for languages in the projects you want to analyze. &lt;code&gt;status&lt;/code&gt; and &lt;code&gt;check&lt;/code&gt; make missing binaries visible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is this a full IDE inside MCP?
&lt;/h3&gt;

&lt;p&gt;No. Locus intentionally exposes six focused tools and complements the host. The project documents symbolic editing, agent memory, and a broad refactoring toolkit as out of scope.&lt;/p&gt;

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

&lt;p&gt;An AI coding agent becomes easier to review when it can cite structure instead of guessing from matching text. Locus provides that bridge with a small MCP surface, explicit language-server prerequisites, and a dry-run-oriented rename flow. Start with one TypeScript project, verify &lt;code&gt;check&lt;/code&gt; and &lt;code&gt;status&lt;/code&gt;, then decide whether the extra semantic evidence improves your agent's refactoring workflow.&lt;/p&gt;

&lt;p&gt;What would you verify first in your own repository: definitions, references, diagnostics, or rename previews?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI assistance disclosure: AI was used to help organize and edit this tutorial. The commands, package metadata, repository documentation, license, and test results were checked against the public Locus sources and a local checkout before publication.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>tutorial</category>
      <category>lsp</category>
    </item>
    <item>
      <title>Install Reviewable Agent Skills with the Tech Leads Club CLI</title>
      <dc:creator>Fernando Paladini</dc:creator>
      <pubDate>Wed, 11 Nov 2026 12:37:45 +0000</pubDate>
      <link>https://dev.to/paladini/install-reviewable-agent-skills-with-the-tech-leads-club-cli-goo</link>
      <guid>https://dev.to/paladini/install-reviewable-agent-skills-with-the-tech-leads-club-cli-goo</guid>
      <description>&lt;p&gt;AI coding agents become more useful when they can follow a repeatable workflow, but copying prompt folders between projects creates a quiet maintenance problem. A skill can be duplicated, overwritten, or installed into the wrong agent directory without leaving much evidence.&lt;/p&gt;

&lt;p&gt;This tutorial uses the open-source &lt;a href="https://github.com/paladini/tlc-agent-skills" rel="noopener noreferrer"&gt;Tech Leads Club agent-skills repository&lt;/a&gt; and its &lt;code&gt;@tech-leads-club/agent-skills&lt;/code&gt; CLI to install one skill into a project. The result is deliberately small: a pinned installation, a copied skill directory, and a lockfile that records what happened.&lt;/p&gt;

&lt;p&gt;The commands below target the repository's stable &lt;code&gt;v1.4.10&lt;/code&gt; tag. That tag declares Node.js 24 or newer and the CLI package is MIT licensed. The repository also contains skill content with separate attribution requirements, so read the license notes before redistributing a skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Use the CLI's non-interactive form when you already know the skill and target agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx --yes @tech-leads-club/agent-skills@1.4.10 install -s tlc-spec-driven -a cursor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the project directory, this installs &lt;code&gt;tlc-spec-driven&lt;/code&gt; as a copy under &lt;code&gt;.cursor/skills&lt;/code&gt; and records the operation under &lt;code&gt;.agents/.skill-lock.json&lt;/code&gt;. The exact result depends on the catalog and the agent configuration available when you run it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 24 or newer, matching the &lt;code&gt;v1.4.10&lt;/code&gt; CLI package engine.&lt;/li&gt;
&lt;li&gt;A project directory where you can create &lt;code&gt;.cursor/&lt;/code&gt; and &lt;code&gt;.agents/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Network access for the first catalog download.&lt;/li&gt;
&lt;li&gt;A compatible agent directory. This example uses Cursor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repository's README lists interactive and command-line workflows. This tutorial uses the command-line path because it is easier to reproduce in a clean directory and does not require raw terminal input.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install one skill locally
&lt;/h2&gt;

&lt;p&gt;Create a temporary project or open an existing project where the skill should live:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mkdir agent-skills-demo
&lt;span class="nb"&gt;cd&lt;/span&gt; agent-skills-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the pinned CLI command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx --yes @tech-leads-club/agent-skills@1.4.10 install -s tlc-spec-driven -a cursor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-s&lt;/code&gt; option selects the skill by catalog name. The &lt;code&gt;-a&lt;/code&gt; option selects the target agent. Because neither &lt;code&gt;-g&lt;/code&gt; nor &lt;code&gt;--global&lt;/code&gt; is supplied, the installation is local to the current project. The default method is copy, which makes the installed files visible in the project and avoids making the project depend on a local symlink.&lt;/p&gt;

&lt;p&gt;On a successful run, the CLI reports that one skill was installed for Cursor. The repository's documented alternative is &lt;code&gt;--symlink&lt;/code&gt;, but that changes the trust and portability model: the target depends on a link that can point somewhere outside the project. Use it only when that relationship is intentional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the result
&lt;/h2&gt;

&lt;p&gt;Inspect the two artifacts created by the operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agent-skills-demo/
  .agents/
    .skill-lock.json
  .cursor/
    skills/
      tlc-spec-driven/
        SKILL.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact skill may contain additional &lt;code&gt;references/&lt;/code&gt; or &lt;code&gt;scripts/&lt;/code&gt; directories. The important checks are that the skill directory is inside the selected agent directory and that the lockfile names the installed skill.&lt;/p&gt;

&lt;p&gt;On Windows PowerShell, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nc"&gt;Get&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;ChildItem&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;Force&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;\&lt;span class="n"&gt;skills&lt;/span&gt;\&lt;span class="n"&gt;tlc&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;driven&lt;/span&gt;
&lt;span class="nc"&gt;Get&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agents&lt;/span&gt;\&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;skill&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lock entry should identify &lt;code&gt;tlc-spec-driven&lt;/code&gt;, the &lt;code&gt;cursor&lt;/code&gt; agent, the &lt;code&gt;copy&lt;/code&gt; method, and a content hash. In my clean checkout, the command completed successfully and created those two project-local paths. Treat the hash as an integrity record for the installed content, not as proof that the skill is safe for every use.&lt;/p&gt;

&lt;p&gt;You can also confirm the CLI version without entering the interactive interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx --yes @tech-leads-club/agent-skills@1.4.10 --version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected version is &lt;code&gt;1.4.10&lt;/code&gt;. Pinning the version makes a future rerun explicit instead of silently selecting a newer package.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the lockfile matters
&lt;/h2&gt;

&lt;p&gt;The project describes &lt;code&gt;.agents/.skill-lock.json&lt;/code&gt; as the source of truth for installed skills. The security policy says the lockfile is validated with a strict schema, written through a backup and temporary file, and populated with content hashes. That gives a team a reviewable record of which catalog item was installed and which content was copied.&lt;/p&gt;

&lt;p&gt;This is more useful than treating a skill directory as an opaque prompt bundle. A reviewer can inspect the &lt;code&gt;SKILL.md&lt;/code&gt; files, compare the lock entry, and decide whether an update should be accepted. The CLI also keeps an audit log for install, update, and remove operations according to the project's security documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security boundaries to understand
&lt;/h2&gt;

&lt;p&gt;The project documents several defenses, but they are boundaries to evaluate, not a universal security guarantee.&lt;/p&gt;

&lt;p&gt;First, the CLI sanitizes names and checks resolved paths before reading, writing, or deleting files. Its security policy specifically describes path traversal and symlink checks. This reduces the risk that a skill name escapes the intended installation directory.&lt;/p&gt;

&lt;p&gt;Second, the catalog is still content that can influence an AI agent. Read the skill before enabling it, especially when it references shell commands, network access, credentials, or external services. A readable file is auditable, but readability does not make every instruction trustworthy.&lt;/p&gt;

&lt;p&gt;Third, the MCP server described by the project is a separate component. The security policy describes it as read-only, local over stdio, and limited to catalog files. Do not generalize those properties to every skill or to the CLI's installation behavior.&lt;/p&gt;

&lt;p&gt;Finally, the repository says some skill files use CC-BY-4.0 while the software engine uses MIT. If you copy a skill into a distribution, check that skill's own attribution and license before publishing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common failure modes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Node.js is too old
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;v1.4.10&lt;/code&gt; package declares &lt;code&gt;node &amp;gt;=24&lt;/code&gt;. An older runtime can fail before the CLI starts. Check &lt;code&gt;node --version&lt;/code&gt; and install a supported Node.js release before debugging the catalog.&lt;/p&gt;

&lt;h3&gt;
  
  
  The skill name is not found
&lt;/h3&gt;

&lt;p&gt;Catalog names are exact. Start with a documented name such as &lt;code&gt;tlc-spec-driven&lt;/code&gt;, and avoid guessing a directory name from a display title. The interactive mode is useful when you need to browse the catalog, but it requires a real terminal with stdin support.&lt;/p&gt;

&lt;h3&gt;
  
  
  The command fails in a redirected process
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;list&lt;/code&gt; command renders an interactive interface. In a non-interactive process without raw stdin support, it can fail before showing the catalog. Use a terminal for browsing, or use the explicit &lt;code&gt;install -s ... -a ...&lt;/code&gt; form when the skill and agent are already known.&lt;/p&gt;

&lt;h3&gt;
  
  
  A symlink behaves differently on another machine
&lt;/h3&gt;

&lt;p&gt;Symlinks depend on filesystem permissions and path layout. The documented default is copy. Prefer the default for a portable project, then review the copied files as part of the change.&lt;/p&gt;

&lt;h3&gt;
  
  
  A rerun changes the installed content
&lt;/h3&gt;

&lt;p&gt;Do not assume the catalog is immutable forever. Pin the CLI version, inspect the lockfile, and review updates before accepting them. The repository's content hashing helps detect changes, but your review still determines whether the new skill belongs in the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does this install a skill globally?
&lt;/h3&gt;

&lt;p&gt;No. The example omits &lt;code&gt;-g&lt;/code&gt;, so it targets the current project. Add the global option only when you intentionally want the skill under the user's agent configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I install more than one skill?
&lt;/h3&gt;

&lt;p&gt;Yes. The README documents multiple names after &lt;code&gt;-s&lt;/code&gt;, for example &lt;code&gt;agent-skills install -s aws-advisor coding-guidelines docs-writer&lt;/code&gt;. Keep the set small enough to review, and verify the resulting lockfile.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does the CLI validate the quality of every skill?
&lt;/h3&gt;

&lt;p&gt;It provides catalog, integrity, and installation controls. Those controls do not replace human review of a skill's instructions, references, scripts, or license.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why pin &lt;code&gt;1.4.10&lt;/code&gt; instead of using &lt;code&gt;npx&lt;/code&gt; without a version?
&lt;/h3&gt;

&lt;p&gt;An unpinned command can resolve a different release later. A versioned command keeps the documented behavior and the verification target aligned.&lt;/p&gt;

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

&lt;p&gt;A local agent skill should be treated like a dependency with a filesystem destination, an integrity record, and a review boundary. Pin the CLI, install with an explicit skill and agent, inspect &lt;code&gt;.cursor/skills&lt;/code&gt; and &lt;code&gt;.agents/.skill-lock.json&lt;/code&gt;, and keep the default copy method unless a symlink is a deliberate choice.&lt;/p&gt;

&lt;p&gt;What would you add to your team's review checklist before allowing a new agent skill into a repository?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI assistance disclosure:&lt;/strong&gt; I used AI assistance to organize and edit this tutorial. The commands, version, installation result, repository details, and security boundaries were checked against the &lt;code&gt;v1.4.10&lt;/code&gt; project sources and a clean local package installation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Primary sources: &lt;a href="https://github.com/paladini/tlc-agent-skills/blob/v1.4.10/README.md" rel="noopener noreferrer"&gt;v1.4.10 README&lt;/a&gt;, &lt;a href="https://github.com/paladini/tlc-agent-skills/blob/v1.4.10/packages/cli/package.json" rel="noopener noreferrer"&gt;CLI package metadata&lt;/a&gt;, &lt;a href="https://github.com/paladini/tlc-agent-skills/blob/v1.4.10/SECURITY.md" rel="noopener noreferrer"&gt;security policy&lt;/a&gt;, &lt;a href="https://github.com/paladini/tlc-agent-skills/blob/v1.4.10/LICENSE" rel="noopener noreferrer"&gt;MIT license and attribution note&lt;/a&gt;, and &lt;a href="https://github.com/paladini/tlc-agent-skills/releases/tag/v1.4.10" rel="noopener noreferrer"&gt;the stable Git tag&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>node</category>
      <category>tutorial</category>
      <category>agents</category>
    </item>
    <item>
      <title>Block Accidental AI Credits Before Git Publishes Them with Go</title>
      <dc:creator>Fernando Paladini</dc:creator>
      <pubDate>Wed, 11 Nov 2026 02:27:05 +0000</pubDate>
      <link>https://dev.to/paladini/block-accidental-ai-credits-before-git-publishes-them-with-go-3c2h</link>
      <guid>https://dev.to/paladini/block-accidental-ai-credits-before-git-publishes-them-with-go-3c2h</guid>
      <description>&lt;p&gt;failed to write kernel assets: The system cannot find the path specified. (os error 3)&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Build a Multilingual React Reflection Portal with GitHub Pages</title>
      <dc:creator>Fernando Paladini</dc:creator>
      <pubDate>Mon, 09 Nov 2026 14:35:08 +0000</pubDate>
      <link>https://dev.to/paladini/build-a-multilingual-react-reflection-portal-with-github-pages-3926</link>
      <guid>https://dev.to/paladini/build-a-multilingual-react-reflection-portal-with-github-pages-3926</guid>
      <description>&lt;p&gt;If a daily-content project starts as a folder of JSON files, the first useful web interface does not need a database server. It needs a predictable data shape, a small client-side state model, and a deployment target that can serve static files.&lt;/p&gt;

&lt;p&gt;This tutorial builds that path with &lt;a href="https://github.com/paladini/aa-daily-reflections-database" rel="noopener noreferrer"&gt;AA Daily Reflections Database&lt;/a&gt;, an open-source React portal that publishes English, Spanish, French, and Brazilian Portuguese data as JSON, CSV, and SQLite. The goal is not to reproduce its reflection text here. The goal is to understand the reusable engineering pattern: select a language, select a date, load one static data file, and deploy the result as a static site.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Clone the repository, install its Node.js dependencies, run the data-copy step, and build the React app. The portal keeps the selected language and date in &lt;code&gt;localStorage&lt;/code&gt;, fetches the matching language JSON from &lt;code&gt;public/data&lt;/code&gt;, and uses &lt;code&gt;PUBLIC_URL&lt;/code&gt; so the same build can work locally or beneath a GitHub Pages project path.&lt;/p&gt;

&lt;p&gt;The repository currently targets React 18.2.0 and &lt;code&gt;react-scripts&lt;/code&gt; 5.0.1 in &lt;a href="https://github.com/paladini/aa-daily-reflections-database/blob/main/package.json" rel="noopener noreferrer"&gt;package.json&lt;/a&gt;. The live site is &lt;a href="https://paladini.github.io/aa-daily-reflections-database/" rel="noopener noreferrer"&gt;available on GitHub Pages&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js and npm&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;A browser&lt;/li&gt;
&lt;li&gt;A GitHub repository if you plan to publish your own fork&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project also includes Python, PHP, Go, and JavaScript examples for reading the SQLite database. This walkthrough focuses on the React portal and its static JSON boundary. See the repository's &lt;a href="https://github.com/paladini/aa-daily-reflections-database/blob/main/examples/README.md" rel="noopener noreferrer"&gt;examples guide&lt;/a&gt; for the other language-specific paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Get the project and inspect the data boundary
&lt;/h2&gt;

&lt;p&gt;Clone the repository and enter it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/paladini/aa-daily-reflections-database.git
&lt;span class="nb"&gt;cd &lt;/span&gt;aa-daily-reflections-database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository stores the browser-facing files in &lt;code&gt;public/data&lt;/code&gt;. Each JSON record has a date, language, title, quote, reflection text, and source reference. The &lt;a href="https://github.com/paladini/aa-daily-reflections-database/blob/main/data/README.md" rel="noopener noreferrer"&gt;data README&lt;/a&gt; documents the same fields and also shows the SQLite schema.&lt;/p&gt;

&lt;p&gt;That separation matters. The React app does not need a server endpoint for every date. It can fetch one language file, keep it in memory, and find the requested date locally. Static hosting is enough for this read-only workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Install dependencies and build the portal
&lt;/h2&gt;

&lt;p&gt;Install from the lockfile, then run the production build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm ci
npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;prebuild&lt;/code&gt; script runs &lt;code&gt;npm run copy-data&lt;/code&gt; first. That script copies the four language files into &lt;code&gt;public/data&lt;/code&gt;, which is the location the browser service reads at runtime. The build then emits a static &lt;code&gt;build&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;If you want to inspect the development version before building, 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 start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the local address shown by Create React App. The application uses the current date as its initial selection and starts in Brazilian Portuguese. You can change both values from the interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. See how language selection becomes a file request
&lt;/h2&gt;

&lt;p&gt;The central mapping lives in &lt;a href="https://github.com/paladini/aa-daily-reflections-database/blob/main/src/context/AppContext.js" rel="noopener noreferrer"&gt;&lt;code&gt;src/context/AppContext.js&lt;/code&gt;&lt;/a&gt;. It maps an application language key to a concrete public filename:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LANGUAGES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pt-br&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;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pt-br&lt;/span&gt;&lt;span class="dl"&gt;'&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Portuguese (Brazil)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;daily_reflections_brazilian-portuguese.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="na"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en&lt;/span&gt;&lt;span class="dl"&gt;'&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;English&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;daily_reflections_english.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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current source contains all four languages. The important design choice is that the UI does not construct arbitrary paths from user input. It selects a file from a fixed allowlist. That keeps the fetch boundary easy to audit.&lt;/p&gt;

&lt;p&gt;The same context stores &lt;code&gt;language&lt;/code&gt; and &lt;code&gt;selectedDate&lt;/code&gt;, then persists them under two local-storage keys. A visitor can return later and keep the last selected language and date on that browser. This is convenience state, not account data, and it never needs a server session.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Load one language file and find one date
&lt;/h2&gt;

&lt;p&gt;The data service in &lt;a href="https://github.com/paladini/aa-daily-reflections-database/blob/main/src/services/reflectionService.js" rel="noopener noreferrer"&gt;&lt;code&gt;src/services/reflectionService.js&lt;/code&gt;&lt;/a&gt; caches each loaded language file. Its core path is equivalent to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dataCache&lt;/span&gt; &lt;span class="o"&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;loadLanguageData&lt;/span&gt; &lt;span class="o"&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;lang&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="nx"&gt;dataCache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lang&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;dataCache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lang&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;languageInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;LANGUAGES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lang&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;baseUrl&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;PUBLIC_URL&lt;/span&gt; &lt;span class="o"&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="nx"&gt;response&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="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;languageInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&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="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;response&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="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="s2"&gt;`Could not load data: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&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="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;response&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="nx"&gt;dataCache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&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;data&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;getReflection&lt;/span&gt; &lt;span class="o"&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;lang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;date&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;data&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;loadLanguageData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lang&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;PUBLIC_URL&lt;/code&gt; prefix is important on GitHub Pages. A project site is served below a path such as &lt;code&gt;/aa-daily-reflections-database&lt;/code&gt;, not necessarily from &lt;code&gt;/&lt;/code&gt;. The prefix lets Create React App provide the correct base path during the build while keeping local development simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Verify the result without trusting the screen alone
&lt;/h2&gt;

&lt;p&gt;Use the build output and the browser network panel as two separate checks.&lt;/p&gt;

&lt;p&gt;First, confirm that the build created the copied files:&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="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; build/data/daily_reflections_english.json
&lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; build/data/daily_reflections_brazilian-portuguese.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On PowerShell, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Test-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build/data/daily_reflections_english.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Test-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build/data/daily_reflections_brazilian-portuguese.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open the app and change the language. In the browser's Network panel, verify that the request changes to the corresponding file under &lt;code&gt;data/&lt;/code&gt;. Change the date and confirm that the displayed record changes without a server-side API request.&lt;/p&gt;

&lt;p&gt;Finally, refresh the page. The selected language and date should be restored because the context writes them to &lt;code&gt;localStorage&lt;/code&gt;. If a JSON request returns 404, inspect the base path first. A missing &lt;code&gt;PUBLIC_URL&lt;/code&gt; is a common static-hosting failure mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Publish the static build
&lt;/h2&gt;

&lt;p&gt;The repository already includes a &lt;code&gt;deploy&lt;/code&gt; script using the &lt;code&gt;gh-pages&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before using it, review the generated &lt;code&gt;build&lt;/code&gt; directory and confirm that the repository's Pages configuration matches the branch and path you intend to publish. GitHub Pages is a static host here. It does not execute the React code on the server and it cannot provide private storage for reflection data or user preferences.&lt;/p&gt;

&lt;p&gt;The project also generates a sitemap as part of its deployment-oriented script. Read &lt;a href="https://github.com/paladini/aa-daily-reflections-database/blob/main/scripts/generate-sitemap.js" rel="noopener noreferrer"&gt;&lt;code&gt;scripts/generate-sitemap.js&lt;/code&gt;&lt;/a&gt; before adapting the domain or path for a fork. SEO metadata is only useful when its canonical URLs match the site that is actually being served.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this pattern works
&lt;/h2&gt;

&lt;p&gt;This architecture matches the problem's access pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The data is read-heavy and changes infrequently.&lt;/li&gt;
&lt;li&gt;Each language can be downloaded independently.&lt;/li&gt;
&lt;li&gt;Date selection is a local lookup over an already loaded array.&lt;/li&gt;
&lt;li&gt;The UI needs no authentication or mutation endpoint.&lt;/li&gt;
&lt;li&gt;The deployment artifact is ordinary HTML, JavaScript, CSS, and JSON.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tradeoff is equally clear. Every browser that uses a language downloads that language's JSON file, and searching the full collection happens in the browser. If the corpus grows substantially, replace the array lookup with a generated index or a query service. Do not add a database server merely because the source data is also available as SQLite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations and security boundaries
&lt;/h2&gt;

&lt;p&gt;The repository's &lt;a href="https://github.com/paladini/aa-daily-reflections-database/blob/main/LICENSE" rel="noopener noreferrer"&gt;MIT license&lt;/a&gt; covers the software compilation and examples, but its legal notice separately says that the original Alcoholics Anonymous materials remain copyrighted and that the compilation is unofficial. Do not treat the MIT notice as permission to republish the underlying literature without checking the rights for your use case. The project links to &lt;a href="https://www.aa.org/" rel="noopener noreferrer"&gt;aa.org&lt;/a&gt; for official resources.&lt;/p&gt;

&lt;p&gt;The portal also is not a medical service, a replacement for a recovery group, or a guarantee that a translation is official. Keep those boundaries visible if you fork it. Do not put private user notes into this static dataset: GitHub Pages publishes the generated files publicly.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do I need a backend?
&lt;/h3&gt;

&lt;p&gt;Not for the included read-only portal. The browser fetches static JSON and performs the date lookup locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why keep JSON, CSV, and SQLite?
&lt;/h3&gt;

&lt;p&gt;They serve different consumers. JSON is convenient for browser and small scripts, CSV is useful for analysis, and SQLite is useful when a client needs indexed queries or joins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I add another language?
&lt;/h3&gt;

&lt;p&gt;Add a validated data file, register it in the language allowlist, copy it into &lt;code&gt;public/data&lt;/code&gt;, and update the UI labels and documentation. Verify dates, encoding, attribution, and rights before publishing.&lt;/p&gt;

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

&lt;p&gt;A multilingual daily-content portal can remain simple when the data contract is explicit. Start with a fixed language allowlist, cache one static file per language, persist only non-sensitive preferences, and make the deployment path part of the verification checklist.&lt;/p&gt;

&lt;p&gt;What would you add first to this pattern: a generated search index, an accessibility test suite, or a small API for clients that cannot download the full language file?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI assistance disclosure: AI assistance was used to organize and edit this tutorial. The repository links, commands, architecture notes, limitations, and security boundaries were checked against the current public repository and live site before publication.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Choose an ActiveAdmin Theme with a Reviewable Rails Checklist</title>
      <dc:creator>Fernando Paladini</dc:creator>
      <pubDate>Wed, 04 Nov 2026 12:40:47 +0000</pubDate>
      <link>https://dev.to/paladini/choose-an-activeadmin-theme-with-a-reviewable-rails-checklist-5eaa</link>
      <guid>https://dev.to/paladini/choose-an-activeadmin-theme-with-a-reviewable-rails-checklist-5eaa</guid>
      <description>&lt;p&gt;ActiveAdmin makes it quick to build an administrative interface in Rails, but the default interface is not always the right fit for a team. The harder part is often not writing CSS. It is choosing a theme without losing track of its source, compatibility, maintenance status, or the small Rails overrides that change behavior.&lt;/p&gt;

&lt;p&gt;This tutorial shows a reviewable way to use &lt;a href="https://github.com/paladini/activeadmin-themes" rel="noopener noreferrer"&gt;activeadmin-themes&lt;/a&gt;: treat the repository as a curated starting point, inspect the linked theme source before installing anything, and keep any local Rails override explicit. The repository is a catalog, not a theme gem or a full Rails application.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Clone the catalog, choose a theme whose upstream repository you can inspect, record the exact dependency and asset instructions from that upstream project, and keep custom behavior in your Rails application. If you want to suppress ActiveAdmin's default CRUD flash messages, the catalog documents a small initializer patch. Verify that patch in your own test environment because the catalog does not test every ActiveAdmin and Rails combination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Rails application that already uses &lt;a href="https://activeadmin.info/" rel="noopener noreferrer"&gt;ActiveAdmin&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Ruby and Bundler working for that application.&lt;/li&gt;
&lt;li&gt;Git, so you can inspect the catalog and the upstream theme repositories.&lt;/li&gt;
&lt;li&gt;A test or staging environment where you can exercise an ActiveAdmin create, update, and destroy action.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The catalog itself is a public MIT-licensed repository owned by Fernando Paladini. Its README is a Markdown list of themes and links. It does not install a theme, select one automatically, or guarantee that a linked project still supports your versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Inspect the catalog as evidence
&lt;/h2&gt;

&lt;p&gt;Start by cloning the exact repository you are reviewing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/paladini/activeadmin-themes.git
&lt;span class="nb"&gt;cd &lt;/span&gt;activeadmin-themes
git log &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'%H %cI %s'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The README currently lists projects such as Active Admin Flat Skin, Active Admin Theme, Active Bootstrap Skin, ActiveMaterial, Arctic Admin, and Fuji Admin. Each entry links to an upstream repository or RubyGems package. That distinction matters: activeadmin-themes is a directory, so the linked project is the source of truth for installation commands and version support.&lt;/p&gt;

&lt;p&gt;Before choosing a theme, open its upstream repository and check at least these items:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The installation instructions and dependency names.&lt;/li&gt;
&lt;li&gt;The supported ActiveAdmin, Rails, Ruby, and asset pipeline versions.&lt;/li&gt;
&lt;li&gt;The license and the latest meaningful activity.&lt;/li&gt;
&lt;li&gt;Whether the project expects Sass, Tailwind, importmaps, Webpacker, or another asset path.&lt;/li&gt;
&lt;li&gt;Whether the screenshots match the current code, rather than only an old release.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not copy a Gemfile line from the catalog because the catalog does not provide one. Copy it only from the upstream project's current documentation or release instructions, then pin and review it like any other dependency.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Keep the application change small
&lt;/h2&gt;

&lt;p&gt;A theme should change the presentation layer. Application-specific behavior should stay in your application. For example, the catalog includes a documented initializer that suppresses ActiveAdmin's default CRUD flash messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/active_admin_disable_default_flash_messages.rb&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_prepare&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;ActiveAdmin&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ResourceController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="kp"&gt;private&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_flash_message&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="c1"&gt;# Keep ActiveAdmin from overriding the flash hash.&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a Ruby monkey patch. It is not a general security control, and it does not remove every notification your application might create. It changes a private controller method so it can become sensitive to ActiveAdmin upgrades. Keep the file small, add a comment explaining why it exists, and review it whenever ActiveAdmin changes.&lt;/p&gt;

&lt;p&gt;If your application needs flash messages for accessibility or workflow feedback, do not apply this patch blindly. A better choice may be to replace the default message with an application-specific message, or to style the existing message through the chosen theme.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Verify the behavior in Rails
&lt;/h2&gt;

&lt;p&gt;Restart the Rails server after adding an initializer. Then exercise one representative resource in a test or staging environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bin/rails db:prepare
bin/rails server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open ActiveAdmin in a browser and test these paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a record and confirm the result is understandable without relying on the default CRUD flash.&lt;/li&gt;
&lt;li&gt;Update a record and confirm validation errors remain visible.&lt;/li&gt;
&lt;li&gt;Destroy a record and confirm the redirect and authorization behavior are unchanged.&lt;/li&gt;
&lt;li&gt;Check a narrow viewport and keyboard navigation after the theme assets load.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The expected result is not simply “the page looks different.” You want a documented theme choice, a reproducible dependency change, and application behavior that remains understandable after the visual change. If you cannot explain where an asset came from or which version supports your ActiveAdmin setup, the review is incomplete.&lt;/p&gt;

&lt;p&gt;For a quick repository-level check, search the catalog for the documented override and inspect the Markdown around it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rg &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; 8 &lt;span class="s2"&gt;"set_flash_message|ActiveAdmin::ResourceController"&lt;/span&gt; README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That command verifies the current catalog contains the example. It does not prove that your Rails application loads the initializer correctly. Only your application's test or staging run can do that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this workflow works
&lt;/h2&gt;

&lt;p&gt;The catalog separates discovery from adoption. Discovery is cheap: you can compare several themes in one place. Adoption is deliberately slower: the upstream project must provide the dependency contract, asset instructions, and compatibility evidence.&lt;/p&gt;

&lt;p&gt;That separation also makes reviews easier. A pull request can show the selected upstream URL, the dependency diff, the asset configuration, and the screenshots or browser checks that justify the choice. Future maintainers do not have to guess whether a line came from the catalog, the theme author, or a local workaround.&lt;/p&gt;

&lt;p&gt;The same principle applies to the flash-message patch. It is useful because it is small and visible, but it is risky because it reaches into a controller implementation detail. Keeping it as a named initializer makes that risk searchable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure modes and honest limits
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The theme is listed but no longer works
&lt;/h3&gt;

&lt;p&gt;The catalog is not a compatibility matrix. A linked repository can be archived, renamed, or designed for an older ActiveAdmin release. Treat every entry as a lead and re-check the upstream source before adding a dependency.&lt;/p&gt;

&lt;h3&gt;
  
  
  The asset pipeline is different
&lt;/h3&gt;

&lt;p&gt;A theme built for Sass is not automatically compatible with a Tailwind-based ActiveAdmin application, and a theme's screenshots do not tell you how it is loaded. Follow the upstream instructions for your asset system and verify the compiled output in a real environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  The patch hides useful feedback
&lt;/h3&gt;

&lt;p&gt;Suppressing default CRUD messages can make an admin workflow harder to understand. Preserve validation errors, authorization failures, and any feedback your operators need. Prefer a replacement message when the workflow requires confirmation.&lt;/p&gt;

&lt;h3&gt;
  
  
  A private API changes
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ActiveAdmin::ResourceController&lt;/code&gt; and &lt;code&gt;set_flash_message&lt;/code&gt; are implementation details for this purpose. A future ActiveAdmin release can change them. Pin versions according to your application's policy, run the relevant tests, and remove the patch if the upstream behavior changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The repository contains old examples
&lt;/h3&gt;

&lt;p&gt;The catalog is a curated list, not a release artifact. Its README can remain useful while some linked projects age. Record the date and commit you reviewed in your project decision log.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does activeadmin-themes install a theme?
&lt;/h3&gt;

&lt;p&gt;No. It catalogs themes and links to their upstream projects. Installation belongs to the selected project's documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the flash-message initializer required?
&lt;/h3&gt;

&lt;p&gt;No. It is an optional documented example. Use it only when your application has a clear reason to change the default behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use a listed theme in production?
&lt;/h3&gt;

&lt;p&gt;The catalog does not make that decision for you. Check the upstream license, maintenance, compatibility, security posture, and your own regression checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  What should I contribute back?
&lt;/h3&gt;

&lt;p&gt;The README asks maintainers to submit a pull request or open an issue when they find a new ActiveAdmin theme. Include the theme's canonical upstream link and enough evidence for another maintainer to review it.&lt;/p&gt;

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

&lt;p&gt;Use activeadmin-themes as a reviewable index, not as a dependency. Choose an upstream project, verify its contract, keep local Rails overrides explicit, and test the admin workflows that your operators actually use.&lt;/p&gt;

&lt;p&gt;Have you found a reliable way to evaluate ActiveAdmin theme compatibility across Rails upgrades without turning your application into a screenshot-only test suite?&lt;/p&gt;

&lt;h2&gt;
  
  
  AI assistance disclosure
&lt;/h2&gt;

&lt;p&gt;AI assistance was used to organize this tutorial and check the Markdown structure. Repository facts, commands, limitations, and the documented Ruby example were checked against the public project sources linked above; no claim of a full Rails application run is made here.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>tutorial</category>
      <category>activeadmin</category>
    </item>
    <item>
      <title>Edit Currículo Lattes XML Safely with TypeScript and lattes-toolkit</title>
      <dc:creator>Fernando Paladini</dc:creator>
      <pubDate>Tue, 03 Nov 2026 12:32:40 +0000</pubDate>
      <link>https://dev.to/paladini/edit-curriculo-lattes-xml-safely-with-typescript-and-lattes-toolkit-3kij</link>
      <guid>https://dev.to/paladini/edit-curriculo-lattes-xml-safely-with-typescript-and-lattes-toolkit-3kij</guid>
      <description>&lt;p&gt;If you maintain a Currículo Lattes, a small correction can become a fragile manual task: export the file, find the right XML element, edit it without breaking the document, and remember what changed before importing it again.&lt;/p&gt;

&lt;p&gt;This tutorial shows a safer local workflow with &lt;a href="https://github.com/paladini/lattes-toolkit" rel="noopener noreferrer"&gt;lattes-toolkit&lt;/a&gt;. We will use the stable &lt;code&gt;v2.4.0&lt;/code&gt; release to read an exported XML file, change one typed field, preserve a backup, and verify the result before the human-controlled import step.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@paladini/lattes-toolkit&lt;/code&gt; is a TypeScript library and CLI for working with XML that you already have. It does not log in to Plataforma Lattes, download a curriculum, or click Import XML for you. That boundary is useful: the file stays local, and the final review remains explicit.&lt;/p&gt;

&lt;p&gt;The core workflow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Export your XML from Plataforma Lattes.&lt;/li&gt;
&lt;li&gt;Parse and inspect it locally.&lt;/li&gt;
&lt;li&gt;Apply a narrow change.&lt;/li&gt;
&lt;li&gt;Confirm the generated XML and keep the automatic backup.&lt;/li&gt;
&lt;li&gt;Review and import the file manually.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The &lt;a href="https://www.gov.br/cnpq/pt-br/acesso-a-informacao/acoes-e-programas/plataforma-lattes" rel="noopener noreferrer"&gt;CNPq description of Plataforma Lattes&lt;/a&gt; explains the platform's role in Brazilian research information. The toolkit is an independent project and is not affiliated with CNPq.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 18 or later.&lt;/li&gt;
&lt;li&gt;An XML or ZIP export that you are allowed to edit.&lt;/li&gt;
&lt;li&gt;A terminal and a text editor.&lt;/li&gt;
&lt;li&gt;A separate copy of the original file before experimenting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This tutorial is pinned to &lt;code&gt;@paladini/lattes-toolkit@2.4.0&lt;/code&gt;, released from the repository's &lt;code&gt;v2.4.0&lt;/code&gt; tag. Pinning the version makes the commands reproducible even if npm's &lt;code&gt;latest&lt;/code&gt; dist-tag moves later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install the pinned CLI
&lt;/h2&gt;

&lt;p&gt;You can install the package in a project or invoke its CLI with &lt;code&gt;npx&lt;/code&gt;:&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; @paladini/lattes-toolkit@2.4.0
npx @paladini/lattes-toolkit@2.4.0 &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package declares Node.js &lt;code&gt;&amp;gt;=18.0.0&lt;/code&gt;, uses the MIT license, and exposes the &lt;code&gt;lattes-toolkit&lt;/code&gt; command. The help output is the quickest way to check that the expected CLI is available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Usage:
  lattes-toolkit init [dir]
  lattes-toolkit parse &amp;lt;file.xml|zip&amp;gt; [--json|--summary]
  lattes-toolkit get &amp;lt;file&amp;gt; &amp;lt;path&amp;gt;
  lattes-toolkit set &amp;lt;file&amp;gt; &amp;lt;path&amp;gt; &amp;lt;value&amp;gt;
  lattes-toolkit serialize &amp;lt;file.json&amp;gt; -o &amp;lt;out.xml&amp;gt;
  lattes-toolkit backup list [dir]
  lattes-toolkit restore [--last|&amp;lt;backup-id&amp;gt;] [dir]
  lattes-toolkit validate &amp;lt;file.xml&amp;gt; [--dtd [file.dtd]]
  lattes-toolkit patch &amp;lt;file.xml&amp;gt; &amp;lt;patches.json&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Inspect an exported curriculum
&lt;/h2&gt;

&lt;p&gt;Export your curriculum through the normal Plataforma Lattes interface and save it as &lt;code&gt;curriculo.xml&lt;/code&gt;. Do not place a real curriculum in a public repository or issue. It contains personal data.&lt;/p&gt;

&lt;p&gt;Start with a summary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx lattes-toolkit@2.4.0 parse curriculo.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need a particular field, use dot and bracket notation. For example, this reads the first journal article title:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx lattes-toolkit@2.4.0 get curriculo.xml bibliographicProduction.journalArticles[0].title
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The field names come from the toolkit's typed model, not from a guess about raw XML capitalization. The repository documents &lt;a href="https://github.com/paladini/lattes-toolkit/blob/v2.4.0/docs/cli.md" rel="noopener noreferrer"&gt;CLI commands&lt;/a&gt; and &lt;a href="https://github.com/paladini/lattes-toolkit/blob/v2.4.0/docs/cobertura-campos.md" rel="noopener noreferrer"&gt;typed field coverage&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make one narrow edit
&lt;/h2&gt;

&lt;p&gt;Suppose the summary needs a correction. Use &lt;code&gt;set&lt;/code&gt; with the exact path and value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx lattes-toolkit@2.4.0 &lt;span class="nb"&gt;set &lt;/span&gt;curriculo.xml identification.summary &lt;span class="s2"&gt;"A corrected professional summary."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command serializes the XML back to the same path. When that destination already exists, the toolkit creates a snapshot under &lt;code&gt;.lattes-backup/&lt;/code&gt; before overwriting it. The &lt;a href="https://github.com/paladini/lattes-toolkit/blob/v2.4.0/docs/backups.md" rel="noopener noreferrer"&gt;backup documentation&lt;/a&gt; describes the manifest, SHA-256 value, retention behavior, and restore commands.&lt;/p&gt;

&lt;p&gt;List the available snapshots:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx lattes-toolkit@2.4.0 backup list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the result is not what you expected, restore the latest snapshot before trying again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx lattes-toolkit@2.4.0 restore &lt;span class="nt"&gt;--last&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For multiple edits, prefer &lt;code&gt;patch&lt;/code&gt; with an allowlist rather than giving an automated process unrestricted access to the document:&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;"allowlist"&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="s2"&gt;"identification.summary"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"patches"&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;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"identification.summary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A corrected professional summary."&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;Save that as &lt;code&gt;patches.json&lt;/code&gt;, then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx lattes-toolkit@2.4.0 patch curriculo.xml patches.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An allowlist is a practical safety boundary. It makes the intended edit reviewable and prevents a broad instruction from silently changing unrelated sections.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify before import
&lt;/h2&gt;

&lt;p&gt;Read the edited field again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx lattes-toolkit@2.4.0 get curriculo.xml identification.summary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The package also includes an XSD-based validation command. It uses &lt;code&gt;xmllint&lt;/code&gt; when that tool is available in your environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx lattes-toolkit@2.4.0 validate curriculo.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validation is not a promise that the Plataforma Lattes interface will accept every file. The project's documentation explicitly describes XSD and optional local DTD checks as environment-dependent, and says that schema validation does not guarantee platform acceptance.&lt;/p&gt;

&lt;p&gt;For a programmatic workflow, the same boundaries are available in TypeScript:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readFileSync&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;node:fs&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;applyCurriculumPatches&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;readCurriculum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;writeCurriculum&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;@paladini/lattes-toolkit&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;curriculum&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;readCurriculum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./curriculo.xml&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;applyCurriculumPatches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;curriculum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;identification.summary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;A corrected professional summary.&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;allowlist&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;identification.summary&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;await&lt;/span&gt; &lt;span class="nf"&gt;writeCurriculum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;curriculum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./curriculo.xml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After writing, parse the file again and inspect the changed field. Then open the XML in the Plataforma Lattes interface, review it, and perform the import yourself. The toolkit does not automate login or submission.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this workflow is safer
&lt;/h2&gt;

&lt;p&gt;The library models supported fields as typed data, while retaining unmapped document content during the XML round trip. That is more deliberate than using a regular expression over XML text. The &lt;code&gt;set&lt;/code&gt; command is useful for one field, while &lt;code&gt;patch&lt;/code&gt; makes a batch change auditable through a JSON file.&lt;/p&gt;

&lt;p&gt;Automatic snapshots address a different risk: editing the right field but disliking the result. The backup is local and should be excluded from Git with &lt;code&gt;.lattes-backup/&lt;/code&gt; in &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The release history also matters. Version &lt;code&gt;2.0.0&lt;/code&gt; introduced breaking model and serialization changes aligned with the repository's versioned XSD. Version &lt;code&gt;2.4.0&lt;/code&gt; added typed technical-production fields. Pinning a release avoids accidentally mixing this tutorial with a future CLI or model change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure modes and limits
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The field path is wrong
&lt;/h3&gt;

&lt;p&gt;A path that is not present in the typed model will not produce the intended update. Start with &lt;code&gt;parse&lt;/code&gt;, consult the &lt;a href="https://github.com/paladini/lattes-toolkit/blob/v2.4.0/docs/cobertura-campos.md" rel="noopener noreferrer"&gt;field coverage documentation&lt;/a&gt;, and make one small change at a time.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;xmllint&lt;/code&gt; is unavailable
&lt;/h3&gt;

&lt;p&gt;The validation command can report that validation was skipped when the required executable or local DTD is missing. Treat that as a missing verification step, not as proof that the file is valid.&lt;/p&gt;

&lt;h3&gt;
  
  
  The platform rejects the import
&lt;/h3&gt;

&lt;p&gt;The toolkit cannot guarantee acceptance by Plataforma Lattes. Keep the original export, inspect the backup, and use the platform's own error message and documentation. Do not use the library to bypass a CAPTCHA, terms of use, or a required manual review.&lt;/p&gt;

&lt;h3&gt;
  
  
  Personal data leaks
&lt;/h3&gt;

&lt;p&gt;Curricula contain personally identifiable information. Work on local copies, do not upload real XML to chat or public issue trackers, and use synthetic or anonymized fixtures for tests. The project explicitly says it does not download a curriculum from the public web or query a CPF.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does this package edit the online curriculum directly?
&lt;/h3&gt;

&lt;p&gt;No. It edits an XML or ZIP file that you already have. Export and import remain manual.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use it with an AI agent?
&lt;/h3&gt;

&lt;p&gt;Yes, but give the agent a narrow allowlist, require a re-parse after writing, and keep the import step under human control. The repository has an &lt;a href="https://github.com/paladini/lattes-toolkit/blob/v2.4.0/docs/integracao-ia.md" rel="noopener noreferrer"&gt;AI-agent integration guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does &lt;code&gt;validate&lt;/code&gt; prove the import will work?
&lt;/h3&gt;

&lt;p&gt;No. It can check against the packaged XSD when &lt;code&gt;xmllint&lt;/code&gt; is available. Platform acceptance has additional behavior outside the toolkit's control.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is this an official CNPq package?
&lt;/h3&gt;

&lt;p&gt;No. It is an independent MIT-licensed project and states that it is not affiliated with CNPq.&lt;/p&gt;

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

&lt;p&gt;Treat a Currículo Lattes export as a local, reviewable data file: pin the tool version, change only named fields, keep automatic snapshots, validate when possible, and import manually after checking the generated XML. That gives you repeatable edits without pretending that a local parser can replace the platform's own review and submission controls.&lt;/p&gt;

&lt;p&gt;Have you found a useful field-level Lattes workflow that benefits from an allowlist and a generated backup?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI assistance disclosure: I used AI assistance to organize and draft this tutorial. The commands, version details, project behavior, limitations, and links were checked against the &lt;code&gt;paladini/lattes-toolkit&lt;/code&gt; &lt;code&gt;v2.4.0&lt;/code&gt; sources and a pinned CLI smoke test before publication.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>typescript</category>
      <category>npm</category>
      <category>tutorial</category>
      <category>xml</category>
    </item>
    <item>
      <title>Find Missing Loading and Error States in Next.js with unhappypath</title>
      <dc:creator>Fernando Paladini</dc:creator>
      <pubDate>Sun, 01 Nov 2026 12:38:09 +0000</pubDate>
      <link>https://dev.to/paladini/find-missing-loading-and-error-states-in-nextjs-with-unhappypath-amn</link>
      <guid>https://dev.to/paladini/find-missing-loading-and-error-states-in-nextjs-with-unhappypath-amn</guid>
      <description>&lt;p&gt;AI coding tools are good at making the successful path visible quickly. They can produce a page that loads data, accepts a form, and responds to a button click while leaving the slower, empty, failed, or repeated paths unfinished.&lt;/p&gt;

&lt;p&gt;That gap is easy to miss in a screenshot. It is also expensive to find manually across every route and component. &lt;code&gt;unhappypath&lt;/code&gt; is a small open-source CLI that scans source code for several of these missing UI states. It does not run a browser or call an LLM. It produces a repeatable score and findings that point to files, lines, and rule IDs.&lt;/p&gt;

&lt;p&gt;This tutorial uses the current &lt;code&gt;main&lt;/code&gt; source at commit &lt;a href="https://github.com/paladini/unhappypath/commit/0827ee0b887fa81b7a1c0d24c97ce715d606e1bf" rel="noopener noreferrer"&gt;&lt;code&gt;0827ee0&lt;/code&gt;&lt;/a&gt;. The repository currently reports version &lt;code&gt;0.1.0&lt;/code&gt; in &lt;code&gt;package.json&lt;/code&gt;, requires Node.js 20 or newer, and is licensed under MIT. There is no public release or tag, so the commands below describe the current repository state rather than a stable release.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Clone the repository, install its dependencies, build it, and scan a project directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/paladini/unhappypath.git
&lt;span class="nb"&gt;cd &lt;/span&gt;unhappypath
npm ci
npm run build
node dist/cli.js /path/to/your-next-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result includes a score from 0 to 100, four dimension scores, and findings such as &lt;code&gt;RTE-01&lt;/code&gt; for a missing loading boundary or &lt;code&gt;QRY-02&lt;/code&gt; for a query without visible error handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You need Git, Node.js 20 or newer, npm, and a local Next.js or React project to inspect. The scanner reads source files from disk. It does not need a running development server, a database, API credentials, or network access after dependencies are installed.&lt;/p&gt;

&lt;p&gt;The project depends on TypeScript for its build and uses Node's built-in test runner. Its package metadata declares MIT licensing and the CLI entry point as &lt;code&gt;dist/cli.js&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the scanner from source
&lt;/h2&gt;

&lt;p&gt;The repository documents &lt;code&gt;npm ci&lt;/code&gt; followed by &lt;code&gt;npm run build&lt;/code&gt;. Run those commands from the repository root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm ci
npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the build, the executable is available at &lt;code&gt;dist/cli.js&lt;/code&gt;. The package also exposes an &lt;code&gt;unhappypath&lt;/code&gt; binary when installed as a package, but building from source makes the inspected commit explicit.&lt;/p&gt;

&lt;p&gt;Run the project's tests before using it in a workflow:&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;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the current checkout, this completed successfully with 18 tests. The test command builds first and then executes the compiled tests, so a successful result covers both the TypeScript compilation and the repository's fixture contracts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scan a project
&lt;/h2&gt;

&lt;p&gt;Point the CLI at the directory that contains the application source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node dist/cli.js /path/to/your-next-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The path is positional. If you omit it, the CLI uses the current directory. For a machine-readable report, add &lt;code&gt;--json&lt;/code&gt;; for a Markdown report, use &lt;code&gt;--markdown&lt;/code&gt;. To make the command fail when the score is below a threshold, use &lt;code&gt;--min-score&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node dist/cli.js &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt; &lt;span class="nt"&gt;--out&lt;/span&gt; unhappypath-report.json &lt;span class="nt"&gt;--min-score&lt;/span&gt; 75
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI documents exit code &lt;code&gt;0&lt;/code&gt; for a successful scan, &lt;code&gt;1&lt;/code&gt; when &lt;code&gt;--min-score&lt;/code&gt; is not met, and &lt;code&gt;2&lt;/code&gt; for a usage error. A threshold is a policy choice, not a claim that the application is production-ready. The scanner only checks the signals implemented by its rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the built-in fixtures to understand the output
&lt;/h2&gt;

&lt;p&gt;The repository includes a deliberately incomplete project and a project with the expected boundaries. Run both demos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run demo:broken
npm run demo:good
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The broken fixture currently reports &lt;code&gt;38/100&lt;/code&gt; and 14 findings. It includes missing route boundaries, a query without loading, error, or empty handling, a form without pending feedback, a fetch effect without loading and error state, and an asynchronous click without a disabled or pending state.&lt;/p&gt;

&lt;p&gt;The good fixture reports &lt;code&gt;100/100&lt;/code&gt; and zero findings. That is useful for checking the command and output format, but it is not evidence that an arbitrary application has complete UX coverage. The fixture is intentionally shaped around the scanner's current heuristics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read findings as repair guidance
&lt;/h2&gt;

&lt;p&gt;The rule IDs are stable within the repository's documented &lt;code&gt;ups-v1&lt;/code&gt; reference. Route checks include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;RTE-01&lt;/code&gt;: a route page has no loading boundary in its segment or ancestors.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RTE-02&lt;/code&gt;: a route has no &lt;code&gt;not-found.tsx&lt;/code&gt; boundary in its segment or ancestors.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RTE-03&lt;/code&gt;: a route has no &lt;code&gt;error.tsx&lt;/code&gt; boundary in its segment or ancestors.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RTE-04&lt;/code&gt;: the application has no root &lt;code&gt;global-error.tsx&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For data and interaction code, query rules look for loading, error, and empty branches. Mutation rules look for pending feedback and empty catches. Form rules look for submitting or disabled behavior. A finding includes a file and line so you can inspect the source rather than guessing what the scanner saw.&lt;/p&gt;

&lt;p&gt;The repository's recommended repair order is routes, queries, mutations, and forms. For example, a Next.js route can inherit an &lt;code&gt;app/loading.tsx&lt;/code&gt;, while a query component should expose loading, error, and empty states near the query result. A scanner finding is a prompt to review the code, not an automatic patch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a conservative CI gate
&lt;/h2&gt;

&lt;p&gt;The README documents a GitHub Actions composite action as well as a direct &lt;code&gt;npx&lt;/code&gt; example. A minimal workflow step is:&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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npx unhappypath . --min-score &lt;/span&gt;&lt;span class="m"&gt;75&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current repository's &lt;code&gt;action.yml&lt;/code&gt; invokes &lt;code&gt;npx --yes unhappypath@0.1.0&lt;/code&gt; and accepts &lt;code&gt;path&lt;/code&gt;, &lt;code&gt;min-score&lt;/code&gt;, and &lt;code&gt;format&lt;/code&gt; inputs. If you use that action, review the pinned version and its source before trusting it in a production workflow. The repository also documents the composite action form:&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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;paladini/unhappypath@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;min-score&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;75"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two examples have different versioning behavior. The direct command from the README uses the package name without a version, while the composite action currently calls &lt;code&gt;0.1.0&lt;/code&gt;. Check the project's release status before adopting either form. At the time of this tutorial, no GitHub release or tag was published.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is useful, and where it stops
&lt;/h2&gt;

&lt;p&gt;The scanner combines two kinds of static checks. A route scanner inspects Next.js App Router files such as &lt;code&gt;page.tsx&lt;/code&gt;, &lt;code&gt;loading.tsx&lt;/code&gt;, &lt;code&gt;error.tsx&lt;/code&gt;, and &lt;code&gt;not-found.tsx&lt;/code&gt;. A code scanner uses the TypeScript AST to inspect patterns such as query hooks, fetch calls inside effects, asynchronous click handlers, and forms.&lt;/p&gt;

&lt;p&gt;That design makes the result deterministic and local. The same source tree can produce the same report without model tokens or external service availability. It also means the tool cannot prove runtime behavior. It does not run a browser, simulate a slow network or a server error, judge the quality of copy, or replace end-to-end tests. Its documented blind spots include deeply nested child-component handling and limited analysis of asynchronous Server Components.&lt;/p&gt;

&lt;p&gt;Security boundaries matter too. The scanner reads files from the path you give it and writes reports or badges only when you request those outputs. It does not claim to validate authorization, input handling, secrets, dependencies, or server-side security. Keep those checks in their appropriate tools and tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does it support every React framework?
&lt;/h3&gt;

&lt;p&gt;Not according to the current documentation. Its route rules target the Next.js App Router, while its code rules inspect selected TypeScript and React patterns. Vue, Nuxt, SvelteKit, and Remix route conventions are listed as future work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does a score of 100 mean the UI is production-ready?
&lt;/h3&gt;

&lt;p&gt;No. It means the current rules found no missing signals in the scanned source. A score does not evaluate visual design, copy, runtime failures, accessibility quality, performance, or business behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use it without installing the repository?
&lt;/h3&gt;

&lt;p&gt;The README documents &lt;code&gt;npx unhappypath /path/to/your-next-app&lt;/code&gt; as the future npm option. The current repository also provides a source checkout path that works today. Verify package publication and version availability before depending on an unpinned command.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;unhappypath&lt;/code&gt; is most useful as an early, deterministic review pass after an AI-assisted implementation. Build it from a known commit, scan the application, inspect each finding, and combine the result with runtime tests and human review. Treat the score as a prioritized checklist rather than a proof of readiness.&lt;/p&gt;

&lt;p&gt;What is the most valuable unhappy-path check for your team: route boundaries, data states, mutation feedback, or form submission behavior?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: This article was prepared with AI assistance. The repository documentation, package metadata, current commit, public GitHub metadata, and local commands described here were checked before publication.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>nextjs</category>
      <category>tutorial</category>
      <category>staticanalysis</category>
    </item>
    <item>
      <title>Add Local Developer Utilities to Any MCP Assistant with DevUtils</title>
      <dc:creator>Fernando Paladini</dc:creator>
      <pubDate>Sat, 31 Oct 2026 12:31:49 +0000</pubDate>
      <link>https://dev.to/paladini/add-local-developer-utilities-to-any-mcp-assistant-with-devutils-27a3</link>
      <guid>https://dev.to/paladini/add-local-developer-utilities-to-any-mcp-assistant-with-devutils-27a3</guid>
      <description>&lt;p&gt;AI assistants are useful at translating intent into actions, but small developer utilities are an easy place for an agent to guess. A guessed hash, a malformed JSON transformation, or a misunderstood JWT timestamp can waste more time than the original task.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/paladini/devutils-mcp-server" rel="noopener noreferrer"&gt;DevUtils MCP Server&lt;/a&gt; provides a local set of 36 developer utilities through the Model Context Protocol (MCP). The server exposes hashing, encoding, ID generation, JWT inspection, JSON formatting, network calculations, and text utilities over standard input and output. The operations run locally and the project documents no external API dependency.&lt;/p&gt;

&lt;p&gt;This tutorial installs the stable &lt;code&gt;v1.1.0&lt;/code&gt; release, connects it to an MCP client, verifies the transport with a real initialize request, and explains where the tool is useful and where it is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Run the server with Node.js 18 or newer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; devutils-mcp-server@1.1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add the same command to your MCP client configuration:&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;"mcpServers"&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;"devutils"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"devutils-mcp-server@1.1.0"&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="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;After restarting the client, it can call tools such as &lt;code&gt;hash_sha256&lt;/code&gt;, &lt;code&gt;json_validate&lt;/code&gt;, &lt;code&gt;generate_uuid&lt;/code&gt;, &lt;code&gt;cidr_calculate&lt;/code&gt;, and &lt;code&gt;text_stats&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 18 or newer. The project recommends Node.js 22 for its current development environment.&lt;/li&gt;
&lt;li&gt;An MCP-compatible client such as Claude Desktop, Cursor, VS Code Copilot, or Windsurf.&lt;/li&gt;
&lt;li&gt;Network access only for the first &lt;code&gt;npx&lt;/code&gt; package download. The server itself uses stdio and is documented as having no external API dependency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The commands below target the published &lt;code&gt;v1.1.0&lt;/code&gt; release. The repository default branch currently contains &lt;code&gt;1.1.1&lt;/code&gt;, so pinning the package version makes this walkthrough reproducible against the stable release that was checked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install and connect the server
&lt;/h2&gt;

&lt;p&gt;The simplest path does not install a global package. &lt;code&gt;npx&lt;/code&gt; downloads the requested package version and starts its executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; devutils-mcp-server@1.1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An MCP server using stdio is not a traditional web service. It waits for JSON-RPC messages on standard input and writes protocol responses to standard output. That is why the client configuration names a command and arguments rather than a URL.&lt;/p&gt;

&lt;p&gt;For Claude Desktop on Windows, the documented configuration file is &lt;code&gt;%APPDATA%\\Claude\\claude_desktop_config.json&lt;/code&gt;. For a workspace-specific VS Code setup, use &lt;code&gt;.vscode/mcp.json&lt;/code&gt; with the VS Code &lt;code&gt;servers&lt;/code&gt; shape:&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;"servers"&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;"devutils"&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;"stdio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"devutils-mcp-server@1.1.0"&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="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;Use the configuration shape required by your client. Claude Desktop, Cursor, and Windsurf use an &lt;code&gt;mcpServers&lt;/code&gt; object in the project documentation, while VS Code uses &lt;code&gt;servers&lt;/code&gt; and includes &lt;code&gt;type: "stdio"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the transport before using tools
&lt;/h2&gt;

&lt;p&gt;You can verify the minimal path without opening a client. Send an MCP &lt;code&gt;initialize&lt;/code&gt; request to the package:&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="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke-test","version":"1.0.0"}}}'&lt;/span&gt; | npx &lt;span class="nt"&gt;-y&lt;/span&gt; devutils-mcp-server@1.1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected result is a JSON-RPC response containing the negotiated protocol version and server information. The server may also write a short status message to stderr. That status line is separate from the JSON-RPC response and should not be parsed as a tool result.&lt;/p&gt;

&lt;p&gt;The release smoke test returned this server identity:&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;"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;"devutils-mcp-server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.1.0"&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;If the command cannot find &lt;code&gt;npx&lt;/code&gt;, check &lt;code&gt;node -v&lt;/code&gt; and &lt;code&gt;npm -v&lt;/code&gt; in the same environment used by your MCP client. GUI clients can inherit a different PATH from your interactive shell.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the tools for bounded operations
&lt;/h2&gt;

&lt;p&gt;Once connected, ask the assistant for a small, inspectable operation. For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use the DevUtils &lt;code&gt;hash_sha256&lt;/code&gt; tool on the exact string &lt;code&gt;hello&lt;/code&gt; and return only the hexadecimal digest.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The server registers tools in eight groups. The release README lists six hash tools, eight encoding tools, four generators, two JWT tools, three formatters, five converters, two network tools, and six text tools. Names follow a predictable &lt;code&gt;&amp;lt;domain&amp;gt;_&amp;lt;operation&amp;gt;&lt;/code&gt; pattern.&lt;/p&gt;

&lt;p&gt;For structured data, a useful request is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use &lt;code&gt;json_validate&lt;/code&gt; on this JSON and explain the error location if it is invalid: &lt;code&gt;{ "enabled": true, }&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For network data, use a non-sensitive example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use &lt;code&gt;cidr_calculate&lt;/code&gt; for &lt;code&gt;192.168.10.0/24&lt;/code&gt; and show the network, broadcast, host range, and host count.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These examples are intentionally bounded. The model chooses an explicit tool and supplies typed arguments, while the server performs the utility operation and returns text. You still need to inspect the result when it matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the MCP boundary helps
&lt;/h2&gt;

&lt;p&gt;The value is not that a local library becomes faster through MCP. Native libraries are usually the right choice inside application code. The value is that an MCP client can call a consistent, typed operation during a multi-step conversation without inventing an implementation.&lt;/p&gt;

&lt;p&gt;DevUtils uses the official TypeScript MCP SDK, Zod schemas for tool inputs, and stdio transport. The source registers each tool category with an &lt;code&gt;McpServer&lt;/code&gt; before connecting a &lt;code&gt;StdioServerTransport&lt;/code&gt;. That keeps the boundary explicit: the assistant can request a named operation, but it does not receive arbitrary code execution through this server.&lt;/p&gt;

&lt;p&gt;The repository also keeps the package small enough to understand. The source is organized under &lt;code&gt;src/tools/&lt;/code&gt; by category, including &lt;code&gt;hash.ts&lt;/code&gt;, &lt;code&gt;formatters.ts&lt;/code&gt;, &lt;code&gt;network.ts&lt;/code&gt;, and &lt;code&gt;text.ts&lt;/code&gt;. That structure makes the project useful as a reference when you want to learn how a TypeScript MCP server wires schemas and handlers together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Important limitations and security boundaries
&lt;/h2&gt;

&lt;p&gt;DevUtils is a utility server, not a general security oracle.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;jwt_decode&lt;/code&gt; and &lt;code&gt;jwt_validate&lt;/code&gt; tool descriptions explicitly say that they do not verify a JWT cryptographic signature. Decoding a token can help inspect its header and payload, but it does not prove that the token was issued by a trusted authority. Never treat a decoded payload as authenticated.&lt;/p&gt;

&lt;p&gt;Hashing tools also have different purposes. MD5 and SHA-1 are available for compatibility and checksums, not for password storage. For passwords, use a dedicated password hashing scheme and an appropriate work factor. DevUtils includes bcrypt operations, but its presence does not remove the need to manage credentials, secrets, and authentication policy correctly.&lt;/p&gt;

&lt;p&gt;The server runs locally through stdio and the security policy says it does not expose HTTP endpoints or send user data to external services. That is a useful boundary, not a universal privacy guarantee. The MCP client still receives the inputs you give it, &lt;code&gt;npx&lt;/code&gt; still downloads a package before first use, and your machine still controls the process environment. Review the package, lock down client permissions, and avoid sending secrets in casual prompts.&lt;/p&gt;

&lt;p&gt;The project is also not the best choice for every workload. If application code needs a hash, JSON parser, or CIDR calculation, use the native library directly. MCP adds a process and protocol boundary, which is valuable for agent workflows but unnecessary for a tight application loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does the server need an API key?
&lt;/h3&gt;

&lt;p&gt;No API key is required by the documented package configuration. The server is started locally with Node.js and uses stdio.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does it work with every AI assistant?
&lt;/h3&gt;

&lt;p&gt;It works with MCP-compatible clients. The README documents Claude Desktop, Cursor, VS Code Copilot, Windsurf, Docker, and other MCP clients. A client without MCP support cannot use this server through the documented path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I use the unpinned &lt;code&gt;npx devutils-mcp-server&lt;/code&gt; command?
&lt;/h3&gt;

&lt;p&gt;You can follow the README's short command, but a pinned version is better for a reproducible setup. Recheck the current release before upgrading.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is JWT validation cryptographic verification?
&lt;/h3&gt;

&lt;p&gt;No. The documented &lt;code&gt;jwt_validate&lt;/code&gt; operation checks structure, JSON, expiration, and signature presence. It does not verify the signature against a key.&lt;/p&gt;

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

&lt;p&gt;DevUtils MCP Server is a practical way to give an MCP-compatible assistant a small, local toolbox with explicit names and typed inputs. Start with the pinned &lt;code&gt;v1.1.0&lt;/code&gt; command, verify the initialize response, and then try one bounded operation such as JSON validation or SHA-256 hashing.&lt;/p&gt;

&lt;p&gt;Have you found a developer utility that an AI assistant repeatedly gets wrong and that would benefit from a typed MCP tool?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;AI assistance disclosure: I used AI assistance to organize and edit this tutorial. The commands, version details, configuration examples, and security boundaries were checked against the project's public documentation, source, release metadata, and a live initialize smoke test.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>mcp</category>
      <category>node</category>
      <category>tutorial</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Build a Test-Gated Manifest V3 Extension for Safe X Tab Automation</title>
      <dc:creator>Fernando Paladini</dc:creator>
      <pubDate>Fri, 30 Oct 2026 12:35:40 +0000</pubDate>
      <link>https://dev.to/paladini/build-a-test-gated-manifest-v3-extension-for-safe-x-tab-automation-3n39</link>
      <guid>https://dev.to/paladini/build-a-test-gated-manifest-v3-extension-for-safe-x-tab-automation-3n39</guid>
      <description>&lt;p&gt;Automating actions in a social timeline is easy to start and surprisingly hard to bound. A browser extension can click the wrong control, run while you are away from the tab, request more permissions than it needs, or hide its failure mode behind a vague counter.&lt;/p&gt;

&lt;p&gt;This tutorial uses &lt;a href="https://github.com/paladini/x-likes-automator" rel="noopener noreferrer"&gt;X Likes Automator&lt;/a&gt;, a small open-source Manifest V3 extension, to show a more reviewable path. You will install the stable &lt;code&gt;v1.1.0&lt;/code&gt; release, inspect the permission boundary, run the repository's 171-test suite, and configure a deliberately conservative first session.&lt;/p&gt;

&lt;p&gt;The goal is not to promise safe or compliant automation. The project warns that automated likes can conflict with X rules and may lead to rate limits, restrictions, or suspension. Treat the extension as an educational, personal-use example and stop if X shows an automation warning.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you will build
&lt;/h2&gt;

&lt;p&gt;You will load an unpacked browser extension into a Chromium browser and use it only in an X tab where you are already signed in. The extension reads visible posts, selects an eligible Like control, and pauses when the tab or window is not active.&lt;/p&gt;

&lt;p&gt;The important design choice is the narrow surface: the extension is not a login client and does not use an X API token. Its manifest requests &lt;code&gt;storage&lt;/code&gt; plus host access for &lt;code&gt;https://x.com/*&lt;/code&gt; and &lt;code&gt;https://twitter.com/*&lt;/code&gt;. The repository's security tests also assert that cookies, passwords, session tokens, and unrelated sites are not collected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Chrome, Brave, or Microsoft Edge with Manifest V3 support.&lt;/li&gt;
&lt;li&gt;A normal browser tab already open at X, with an account you control already signed in.&lt;/li&gt;
&lt;li&gt;Node.js 20 or newer if you want to run the tests. Node is not required to run the unpacked extension.&lt;/li&gt;
&lt;li&gt;A clone or the &lt;a href="https://github.com/paladini/x-likes-automator/releases/tag/v1.1.0" rel="noopener noreferrer"&gt;official &lt;code&gt;v1.1.0&lt;/code&gt; release archive&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not paste credentials into the repository or into the extension. Do not use a browser profile that contains accounts you are not authorized to operate.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Verify the release locally
&lt;/h2&gt;

&lt;p&gt;Clone the exact stable tag instead of relying on the moving default branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;--branch&lt;/span&gt; v1.1.0 &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 https://github.com/paladini/x-likes-automator.git
&lt;span class="nb"&gt;cd &lt;/span&gt;x-likes-automator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tag is the reproducibility anchor for this tutorial. The README and release page identify &lt;code&gt;v1.1.0&lt;/code&gt;, while the license file identifies the project as MIT.&lt;/p&gt;

&lt;p&gt;Run the included checks:&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;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The release currently reports 171 passing tests covering the manifest, permissions, settings, visible-page behavior, pause and resume rules, refusal list, localization, documentation, and policy engine. A green run does not prove that X accepts automated activity. It only checks the tagged project behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Load the extension without a build step
&lt;/h2&gt;

&lt;p&gt;Open the extensions page for your browser. In Chrome, the documented address is &lt;code&gt;chrome://extensions&lt;/code&gt;; the equivalent pages are &lt;code&gt;brave://extensions&lt;/code&gt; and &lt;code&gt;edge://extensions&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable Developer mode.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Load unpacked&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select the cloned &lt;code&gt;x-likes-automator&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;Confirm that the selected directory contains &lt;code&gt;manifest.json&lt;/code&gt; at its top level.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The repository deliberately has no bundling step. Loading the source directory makes the reviewed JavaScript, manifest, popup, icons, and tests visible. If the browser reports a manifest error, stop and inspect the selected directory instead of downloading a random replacement archive.&lt;/p&gt;

&lt;p&gt;Pin &lt;strong&gt;X Likes Automator&lt;/strong&gt; in the browser toolbar, then open &lt;code&gt;https://x.com/home&lt;/code&gt; in a regular tab. The extension does not open a new window or perform a login flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Configure a conservative first session
&lt;/h2&gt;

&lt;p&gt;The popup is localized in Brazilian Portuguese. The setting names below are the behavior documented by the release:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;v1.1.0&lt;/code&gt; behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Interval&lt;/td&gt;
&lt;td&gt;Integer from 1 to 60 seconds; default 5 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interval jitter&lt;/td&gt;
&lt;td&gt;A 5-second setting chooses a delay from 4.25 to 5 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Optional cap&lt;/td&gt;
&lt;td&gt;1 to 1,000,000 when enabled; default stored cap is 100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Original post in a reply thread&lt;/td&gt;
&lt;td&gt;Enabled by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plain repost&lt;/td&gt;
&lt;td&gt;Enabled by default, but can be turned off&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a first review, set the interval to 60 seconds, enable a cap such as 5, and turn off &lt;strong&gt;Repostar também&lt;/strong&gt;. This keeps the session focused on the Like path and gives you a short, observable run. The cap is a control for your account activity, not a guarantee of compliance.&lt;/p&gt;

&lt;p&gt;Press &lt;strong&gt;Iniciar&lt;/strong&gt; while the active tab is an X home timeline, profile, search result, or single-post thread. If the active tab is another site, the project refuses to start.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Observe the policy boundary
&lt;/h2&gt;

&lt;p&gt;The extension separates decisions from DOM operations. &lt;code&gt;lib/policy.js&lt;/code&gt; is a pure decision engine that validates settings, classifies the current X surface, chooses the next eligible post, and decides whether to like, scroll, wait, pause, or stop. &lt;code&gt;content/content.js&lt;/code&gt; adapts those decisions to the page.&lt;/p&gt;

&lt;p&gt;That separation gives you useful review points. The extension should pause when the document is hidden or the window loses focus. It should skip a post that is already liked, remember a post within the current session, and refuse unsupported pages such as messages, settings, notifications, and compose screens.&lt;/p&gt;

&lt;p&gt;The project also keeps an explicit refusal list. It does not click reply, quote, follow, direct message, login, or cookie controls. When the optional repost behavior is enabled, it only targets the plain repost menu item and never opens a composer.&lt;/p&gt;

&lt;p&gt;If X displays an unusual-activity notice, the documented behavior is to wait 60 seconds and resume. The extension does not attempt to bypass that protection. You should still stop the session if the notice repeats or if the account's activity looks unexpected.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Verify the result and stop cleanly
&lt;/h2&gt;

&lt;p&gt;During a small capped run, check the popup counter against the visible page. Switch away from the X tab and confirm that the status changes to paused. Return to the tab and confirm that the same session count resumes. Then click &lt;strong&gt;Parar&lt;/strong&gt; before investigating anything else.&lt;/p&gt;

&lt;p&gt;The repository documents a five-minute page reload cycle that preserves the session count through browser storage. A browser restart clears the volatile run state, so starting again requires an explicit button press. These details matter because a persistent background worker would have a different risk profile.&lt;/p&gt;

&lt;p&gt;After stopping, inspect the browser's extension details and the repository's &lt;code&gt;manifest.json&lt;/code&gt;. The expected permission shape is small:&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;"permissions"&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="s2"&gt;"storage"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"host_permissions"&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="s2"&gt;"https://x.com/*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://twitter.com/*"&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;This snippet is a review aid, not a complete manifest. Compare it with the actual release file rather than replacing the file with a hand-written version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure modes worth checking
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The popup says the tab is unsupported.&lt;/strong&gt; Navigate to the X home timeline, a profile, a search result, or a thread. The extension intentionally ignores messages, notifications, settings, explore, compose, and non-X pages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The session stays paused.&lt;/strong&gt; Make the X tab visible and focus the browser window. The pause is presence-based by design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The session does not start after installation.&lt;/strong&gt; Reload the X tab and press &lt;strong&gt;Iniciar&lt;/strong&gt; again. A tab that existed before installation may not yet have the content script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The browser shows an automation warning.&lt;/strong&gt; Do not increase the rate or try to evade the warning. Stop the session, review the account impact, and consult X's current rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The test suite fails.&lt;/strong&gt; Do not treat a partial result as release validation. Check the Node version, rerun &lt;code&gt;npm test&lt;/code&gt; from the tagged checkout, and compare the local files with the official release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security and privacy boundaries
&lt;/h2&gt;

&lt;p&gt;The extension's narrow permissions reduce exposure, but they do not remove the central risk: it acts on a signed-in social account. The project cannot guarantee that a selector will remain correct as X changes its interface, and it cannot guarantee that automated activity is allowed for your account.&lt;/p&gt;

&lt;p&gt;Review the &lt;a href="https://github.com/paladini/x-likes-automator/blob/v1.1.0/SECURITY.md" rel="noopener noreferrer"&gt;security policy&lt;/a&gt; before reporting a vulnerability. Keep the account logged in only in a profile you control, use a small cap, keep the interval conservative, and remove the unpacked extension when you finish evaluating it.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does it need an X API key?
&lt;/h3&gt;

&lt;p&gt;No. The documented design uses the open X tab and does not request an API token or credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does it run on every website?
&lt;/h3&gt;

&lt;p&gt;No. The manifest host permissions and content-script matches are limited to X and Twitter origins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is a passing test suite a safety guarantee?
&lt;/h3&gt;

&lt;p&gt;No. It verifies repository behavior at the checked-out release. It cannot predict policy enforcement, account restrictions, or future changes to X's DOM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I make it run unattended?
&lt;/h3&gt;

&lt;p&gt;The project is designed around an active, visible tab and pauses when the tab is inactive. Do not treat its reload behavior as permission to leave account automation unattended.&lt;/p&gt;

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

&lt;p&gt;The useful lesson in X Likes Automator is not that social activity can be automated. It is that a browser extension becomes easier to review when its release is pinned, permissions are explicit, decisions are tested, unsupported actions are refused, and stopping behavior is visible.&lt;/p&gt;

&lt;p&gt;If you try the tagged release, keep the first session small and verify each state transition yourself. Which boundary would you want to see tested before trusting a browser extension that acts on a signed-in account?&lt;/p&gt;

&lt;p&gt;Disclosure: AI assistance was used to organize and edit this tutorial. The repository behavior, release metadata, documentation, license, configuration, and test result described here were checked against the public &lt;code&gt;v1.1.0&lt;/code&gt; sources.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>extensions</category>
      <category>tutorial</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
