CmdForge/reference/registry-spec/index.html

840 lines
498 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en" dir="ltr" class="docs-wrapper plugin-docs plugin-id-default docs-version-current docs-doc-page docs-doc-id-reference/registry-spec" data-has-hydrated="false">
<head>
<meta charset="UTF-8">
<meta name="generator" content="Docusaurus v3.9.2">
<title data-rh="true">CmdForge Registry Design | CmdForge</title><meta data-rh="true" name="viewport" content="width=device-width,initial-scale=1"><meta data-rh="true" name="twitter:card" content="summary_large_image"><meta data-rh="true" property="og:url" content="https://pages.brrd.tech/rob/CmdForge/reference/registry-spec"><meta data-rh="true" property="og:locale" content="en"><meta data-rh="true" name="docusaurus_locale" content="en"><meta data-rh="true" name="docsearch:language" content="en"><meta data-rh="true" name="docusaurus_version" content="current"><meta data-rh="true" name="docusaurus_tag" content="docs-default-current"><meta data-rh="true" name="docsearch:version" content="current"><meta data-rh="true" name="docsearch:docusaurus_tag" content="docs-default-current"><meta data-rh="true" property="og:title" content="CmdForge Registry Design | CmdForge"><meta data-rh="true" name="description" content="Purpose"><meta data-rh="true" property="og:description" content="Purpose"><link data-rh="true" rel="icon" href="/rob/CmdForge/img/favicon.ico"><link data-rh="true" rel="canonical" href="https://pages.brrd.tech/rob/CmdForge/reference/registry-spec"><link data-rh="true" rel="alternate" href="https://pages.brrd.tech/rob/CmdForge/reference/registry-spec" hreflang="en"><link data-rh="true" rel="alternate" href="https://pages.brrd.tech/rob/CmdForge/reference/registry-spec" hreflang="x-default"><script data-rh="true" type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Reference","item":"https://pages.brrd.tech/rob/CmdForge/category/reference"},{"@type":"ListItem","position":2,"name":"Registry API","item":"https://pages.brrd.tech/rob/CmdForge/reference/registry-spec"}]}</script><link rel="stylesheet" href="/rob/CmdForge/assets/css/styles.37cb0314.css">
<script src="/rob/CmdForge/assets/js/runtime~main.a8ea285c.js" defer="defer"></script>
<script src="/rob/CmdForge/assets/js/main.af70b7f2.js" defer="defer"></script>
</head>
<body class="navigation-with-keyboard">
<svg style="display: none;"><defs>
<symbol id="theme-svg-external-link" viewBox="0 0 24 24"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"/></symbol>
</defs></svg>
<script>!function(){var t=function(){try{return new URLSearchParams(window.location.search).get("docusaurus-theme")}catch(t){}}()||function(){try{return window.localStorage.getItem("theme")}catch(t){}}();document.documentElement.setAttribute("data-theme",t||(window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light")),document.documentElement.setAttribute("data-theme-choice",t||"system")}(),function(){try{const c=new URLSearchParams(window.location.search).entries();for(var[t,e]of c)if(t.startsWith("docusaurus-data-")){var a=t.replace("docusaurus-data-","data-");document.documentElement.setAttribute(a,e)}}catch(t){}}()</script><div id="__docusaurus"><div role="region" aria-label="Skip to main content"><a class="skipToContent_fXgn" href="#__docusaurus_skipToContent_fallback">Skip to main content</a></div><nav aria-label="Main" class="theme-layout-navbar navbar navbar--fixed-top"><div class="navbar__inner"><div class="theme-layout-navbar-left navbar__items"><button aria-label="Toggle navigation bar" aria-expanded="false" class="navbar__toggle clean-btn" type="button"><svg width="30" height="30" viewBox="0 0 30 30" aria-hidden="true"><path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M4 7h22M4 15h22M4 23h22"></path></svg></button><a class="navbar__brand" href="/rob/CmdForge/"><b class="navbar__title text--truncate">CmdForge</b></a></div><div class="theme-layout-navbar-right navbar__items navbar__items--right"><a href="https://gitea.brrd.tech/rob/CmdForge" target="_blank" rel="noopener noreferrer" class="navbar__item navbar__link">Source Code<svg width="13.5" height="13.5" aria-label="(opens in new tab)" class="iconExternalLink_nPIU"><use href="#theme-svg-external-link"></use></svg></a><div class="toggle_vylO colorModeToggle_DEke"><button class="clean-btn toggleButton_gllP toggleButtonDisabled_aARS" type="button" disabled="" title="system mode" aria-label="Switch between dark and light mode (currently system mode)"><svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" class="toggleIcon_g3eP lightToggleIcon_pyhR"><path fill="currentColor" d="M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"></path></svg><svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" class="toggleIcon_g3eP darkToggleIcon_wfgR"><path fill="currentColor" d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"></path></svg><svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" class="toggleIcon_g3eP systemToggleIcon_QzmC"><path fill="currentColor" d="m12 21c4.971 0 9-4.029 9-9s-4.029-9-9-9-9 4.029-9 9 4.029 9 9 9zm4.95-13.95c1.313 1.313 2.05 3.093 2.05 4.95s-0.738 3.637-2.05 4.95c-1.313 1.313-3.093 2.05-4.95 2.05v-14c1.857 0 3.637 0.737 4.95 2.05z"></path></svg></button></div><div class="navbarSearchContainer_Bca1"></div></div></div><div role="presentation" class="navbar-sidebar__backdrop"></div></nav><div id="__docusaurus_skipToContent_fallback" class="theme-layout-main main-wrapper mainWrapper_z2l0"><div class="docsWrapper_hBAB"><button aria-label="Scroll back to top" class="clean-btn theme-back-to-top-button backToTopButton_sjWU" type="button"></button><div class="docRoot_UBD9"><aside class="theme-doc-sidebar-container docSidebarContainer_YfHR"><div class="sidebarViewport_aRkj"><div class="sidebar_njMd"><nav aria-label="Docs sidebar" class="menu thin-scrollbar menu_SIkG"><ul class="theme-doc-sidebar-menu menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/rob/CmdForge/"><span title="CmdForge Overview" class="linkLabel_WmDU">CmdForge Overview</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/rob/CmdForge/architecture"><span title="CmdForge Architecture" class="linkLabel_WmDU">CmdForge Architecture</span></a></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item"><div class="menu__list-item-collapsible"><a class="categoryLink_byQd menu__link menu__link--sublist menu__link--active" href="/rob/CmdForge/category/reference"><span title="Reference" class="categoryLinkLabel_W154">Reference</span></a><button aria-label="Collapse sidebar category &#x27;Reference&#x27;" aria-expanded="true" type="button" class="clean-btn menu__caret"></button></div><ul class="menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/rob/CmdForge/reference/providers"><span title="Provider Setup" class="linkLabel_WmDU">Provider Setup</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link menu__link--active" aria-current="page" tabindex="0" href="/rob/CmdForge/reference/registry-spec"><span title="Registry API" class="linkLabel_WmDU">Registry API</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/rob/CmdForge/reference/meta-tools"><span title="Meta-Tools" class="linkLabel_WmDU">Meta-Tools</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/rob/CmdForge/reference/collections"><span title="Collections" class="linkLabel_WmDU">Collections</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/rob/CmdForge/reference/examples"><span title="Example Tools" class="linkLabel_WmDU">Example Tools</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/rob/CmdForge/reference/design"><span title="Design Philosophy" class="linkLabel_WmDU">Design Philosophy</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/rob/CmdForge/reference/web-ui-spec"><span title="Web UI Design" class="linkLabel_WmDU">Web UI Design</span></a></li></ul></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/rob/CmdForge/todos"><span title="CmdForge TODOs" class="linkLabel_WmDU">CmdForge TODOs</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/rob/CmdForge/goals"><span title="Goals" class="linkLabel_WmDU">Goals</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/rob/CmdForge/ideas-and-exploration"><span title="Ideas &amp; Exploration" class="linkLabel_WmDU">Ideas &amp; Exploration</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/rob/CmdForge/milestones"><span title="Milestones" class="linkLabel_WmDU">Milestones</span></a></li></ul></nav></div></div></aside><main class="docMainContainer_TBSr"><div class="container padding-top--md padding-bottom--lg"><div class="row"><div class="col docItemCol_VOVn"><div class="docItemContainer_Djhp"><article><nav class="theme-doc-breadcrumbs breadcrumbsContainer_Z_bl" aria-label="Breadcrumbs"><ul class="breadcrumbs"><li class="breadcrumbs__item"><a aria-label="Home page" class="breadcrumbs__link" href="/rob/CmdForge/"><svg viewBox="0 0 24 24" class="breadcrumbHomeIcon_YNFT"><path d="M10 19v-5h4v5c0 .55.45 1 1 1h3c.55 0 1-.45 1-1v-7h1.7c.46 0 .68-.57.33-.87L12.67 3.6c-.38-.34-.96-.34-1.34 0l-8.36 7.53c-.34.3-.13.87.33.87H5v7c0 .55.45 1 1 1h3c.55 0 1-.45 1-1z" fill="currentColor"></path></svg></a></li><li class="breadcrumbs__item"><a class="breadcrumbs__link" href="/rob/CmdForge/category/reference"><span>Reference</span></a></li><li class="breadcrumbs__item breadcrumbs__item--active"><span class="breadcrumbs__link">Registry API</span></li></ul></nav><div class="tocCollapsible_ETCw theme-doc-toc-mobile tocMobile_ITEo"><button type="button" class="clean-btn tocCollapsibleButton_TO0P">On this page</button></div><div class="theme-doc-markdown markdown"><header><h1>CmdForge Registry Design</h1></header>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="purpose">Purpose<a href="#purpose" class="hash-link" aria-label="Direct link to Purpose" title="Direct link to Purpose" translate="no"></a></h2>
<p>Build a centralized registry for CmdForge to enable discovery, publishing, dependency management, and future curation at scale.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="terminology">Terminology<a href="#terminology" class="hash-link" aria-label="Direct link to Terminology" title="Direct link to Terminology" translate="no"></a></h2>
<table><thead><tr><th>Term</th><th>Definition</th></tr></thead><tbody><tr><td><strong>Tool definition</strong></td><td>The full YAML file in the registry (<code>config.yaml</code>) containing name, steps, arguments, etc.</td></tr><tr><td><strong>Tool config</strong></td><td>The configuration within a tool definition (arguments, steps, provider settings)</td></tr><tr><td><strong>cmdforge.yaml</strong></td><td>Project manifest file declaring tool dependencies and overrides</td></tr><tr><td><strong>config.yaml</strong></td><td>The tool definition file, both in registry and when installed locally</td></tr><tr><td><strong>Owner</strong></td><td>Immutable namespace slug identifying the publisher (e.g., <code>rob</code>, <code>alice</code>)</td></tr><tr><td><strong>Publisher</strong></td><td>A registered user who can publish tools to the registry</td></tr><tr><td><strong>Wrapper script</strong></td><td>Auto-generated bash script in <code>~/.local/bin/</code> that invokes a tool</td></tr></tbody></table>
<p><strong>Canonical naming:</strong> Use <code>CmdForge-Registry</code> (capitalized, hyphenated) for the repository name.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="diagram-references">Diagram References<a href="#diagram-references" class="hash-link" aria-label="Direct link to Diagram References" title="Direct link to Diagram References" translate="no"></a></h2>
<ul>
<li class="">System overview: <code>discussions/diagrams/cmdforge-registry_rob_1.puml</code></li>
<li class="">Data flows: <code>discussions/diagrams/cmdforge-registry_rob_5.puml</code></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="system-overview">System Overview<a href="#system-overview" class="hash-link" aria-label="Direct link to System Overview" title="Direct link to System Overview" translate="no"></a></h2>
<p>Users interact via the CLI and a future Web UI. Both call a Registry API hosted at <code>https://cmdforge.brrd.tech/api/v1</code> (future alias: <code>cmdforge.brrd.tech/api/v1</code>). The API syncs from a Gitea-backed registry repo and maintains a SQLite cache/search index.</p>
<p><strong>Canonical API base path:</strong> <code>https://cmdforge.brrd.tech/api/v1</code></p>
<p>All API endpoints are versioned under <code>/api/v1</code>. When breaking changes are needed, a new version (<code>/api/v2</code>) will be introduced with deprecation notices.</p>
<p>Core API endpoints:</p>
<ul>
<li class=""><code>GET /api/v1/tools</code></li>
<li class=""><code>GET /api/v1/tools/search?q=...</code> (with advanced filtering)</li>
<li class=""><code>GET /api/v1/tools/{owner}/{name}</code></li>
<li class=""><code>GET /api/v1/tools/{owner}/{name}/versions</code></li>
<li class=""><code>GET /api/v1/tools/{owner}/{name}/download?version=...</code></li>
<li class=""><code>POST /api/v1/tools</code> (publish)</li>
<li class=""><code>GET /api/v1/categories</code></li>
<li class=""><code>GET /api/v1/tags</code> (list all tags with counts)</li>
<li class=""><code>GET /api/v1/collections</code></li>
<li class=""><code>GET /api/v1/collections/{name}</code></li>
<li class=""><code>GET /api/v1/stats/popular</code></li>
<li class=""><code>POST /api/v1/webhook/gitea</code></li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="pagination">Pagination<a href="#pagination" class="hash-link" aria-label="Direct link to Pagination" title="Direct link to Pagination" translate="no"></a></h3>
<p>All list endpoints support pagination:</p>
<table><thead><tr><th>Parameter</th><th>Default</th><th>Max</th><th>Description</th></tr></thead><tbody><tr><td><code>page</code></td><td>1</td><td>-</td><td>Page number (1-indexed)</td></tr><tr><td><code>per_page</code></td><td>20</td><td>100</td><td>Items per page</td></tr><tr><td><code>sort</code></td><td><code>downloads</code></td><td>-</td><td>Sort field</td></tr><tr><td><code>order</code></td><td><code>desc</code></td><td>-</td><td>Sort order (asc/desc)</td></tr></tbody></table>
<p><strong>Stable ordering:</strong> To ensure deterministic results across pages, sorting includes a secondary key:</p>
<ul>
<li class="">Primary: requested field (e.g., <code>downloads</code>)</li>
<li class="">Secondary: <code>published_at</code> (desc)</li>
<li class="">Tertiary: <code>id</code> (for absolute stability)</li>
</ul>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ORDER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BY</span><span class="token plain"> downloads </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> published_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">LIMIT</span><span class="token plain"> </span><span class="token number">20</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">OFFSET</span><span class="token plain"> </span><span class="token number">0</span><br></span></code></pre></div></div>
<p><strong>Response pagination metadata:</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;data&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">...</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;meta&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;page&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;per_page&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">20</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;total&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">142</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;total_pages&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">8</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="input-constraints">Input Constraints<a href="#input-constraints" class="hash-link" aria-label="Direct link to Input Constraints" title="Direct link to Input Constraints" translate="no"></a></h3>
<p>Size limits to prevent oversized uploads:</p>
<table><thead><tr><th>Field</th><th>Max Size</th><th>Notes</th></tr></thead><tbody><tr><td><code>config.yaml</code></td><td>64 KB</td><td>Tool definition</td></tr><tr><td><code>README.md</code></td><td>256 KB</td><td>Documentation</td></tr><tr><td>Request body</td><td>512 KB</td><td>Total POST payload</td></tr><tr><td>Tool name</td><td>64 chars</td><td>Alphanumeric + hyphen</td></tr><tr><td>Description</td><td>500 chars</td><td>Short summary</td></tr><tr><td>Tag</td><td>32 chars</td><td>Individual tag</td></tr><tr><td>Tags array</td><td>10 items</td><td>Maximum tags per tool</td></tr></tbody></table>
<p><strong>Validation errors:</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;error&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;code&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;PAYLOAD_TOO_LARGE&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;message&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;config.yaml exceeds 64KB limit&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;details&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;field&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;config&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;size&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">72000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;limit&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">65536</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="sort-fields-and-indexes">Sort Fields and Indexes<a href="#sort-fields-and-indexes" class="hash-link" aria-label="Direct link to Sort Fields and Indexes" title="Direct link to Sort Fields and Indexes" translate="no"></a></h3>
<p><strong>Allowed sort fields:</strong></p>
<table><thead><tr><th>Endpoint</th><th>Allowed <code>sort</code> values</th></tr></thead><tbody><tr><td><code>GET /tools</code></td><td><code>downloads</code>, <code>published_at</code>, <code>name</code></td></tr><tr><td><code>GET /tools/search</code></td><td><code>relevance</code>, <code>downloads</code>, <code>published_at</code></td></tr><tr><td><code>GET /categories</code></td><td><code>name</code>, <code>tool_count</code></td></tr></tbody></table>
<p>Invalid sort values return 400:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">&quot;error&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">&quot;code&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;INVALID_SORT&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">&quot;message&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Unknown sort field &#x27;foo&#x27;. Allowed: downloads, published_at, name&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="tags-endpoint">Tags Endpoint<a href="#tags-endpoint" class="hash-link" aria-label="Direct link to Tags Endpoint" title="Direct link to Tags Endpoint" translate="no"></a></h3>
<p><strong><code>GET /api/v1/tags</code></strong> - List all tags with usage counts.</p>
<table><thead><tr><th>Parameter</th><th>Default</th><th>Description</th></tr></thead><tbody><tr><td><code>category</code></td><td>-</td><td>Filter tags to those used in a specific category</td></tr><tr><td><code>limit</code></td><td>100</td><td>Maximum tags to return (max 500)</td></tr></tbody></table>
<p><strong>Response:</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;data&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">&quot;name&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;cli&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">&quot;count&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">45</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">&quot;name&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;ai&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">&quot;count&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">32</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">&quot;name&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;text&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">&quot;count&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">28</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;meta&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">&quot;total&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">87</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="advanced-search">Advanced Search<a href="#advanced-search" class="hash-link" aria-label="Direct link to Advanced Search" title="Direct link to Advanced Search" translate="no"></a></h3>
<p><strong><code>GET /api/v1/tools/search</code></strong> supports advanced filtering beyond basic text search.</p>
<table><thead><tr><th>Parameter</th><th>Type</th><th>Default</th><th>Description</th></tr></thead><tbody><tr><td><code>q</code></td><td>string</td><td>required</td><td>Search query (uses FTS5 full-text search)</td></tr><tr><td><code>category</code></td><td>string</td><td>-</td><td>Filter by single category</td></tr><tr><td><code>categories</code></td><td>string</td><td>-</td><td>Filter by multiple categories (comma-separated, OR logic)</td></tr><tr><td><code>tags</code></td><td>string</td><td>-</td><td>Filter by tags (comma-separated, AND logic)</td></tr><tr><td><code>owner</code></td><td>string</td><td>-</td><td>Filter by publisher/owner</td></tr><tr><td><code>min_downloads</code></td><td>int</td><td>-</td><td>Minimum download count</td></tr><tr><td><code>max_downloads</code></td><td>int</td><td>-</td><td>Maximum download count</td></tr><tr><td><code>published_after</code></td><td>date</td><td>-</td><td>Published after date (ISO 8601: YYYY-MM-DD)</td></tr><tr><td><code>published_before</code></td><td>date</td><td>-</td><td>Published before date (ISO 8601: YYYY-MM-DD)</td></tr><tr><td><code>deprecated</code></td><td>bool</td><td>false</td><td>Include deprecated tools</td></tr><tr><td><code>include_facets</code></td><td>bool</td><td>false</td><td>Include faceted counts in response</td></tr><tr><td><code>sort</code></td><td>string</td><td>relevance</td><td>Sort by: relevance, downloads, published_at, name</td></tr><tr><td><code>page</code></td><td>int</td><td>1</td><td>Page number</td></tr><tr><td><code>per_page</code></td><td>int</td><td>20</td><td>Results per page (max 100)</td></tr></tbody></table>
<p><strong>Tag filtering (AND logic):</strong> When multiple tags are specified, only tools with ALL tags are returned:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">GET /api/v1/tools/search?q=summarize&amp;tags=cli,ai</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Returns tools that have BOTH &quot;cli&quot; AND &quot;ai&quot; tags</span><br></span></code></pre></div></div>
<p><strong>Category filtering (OR logic):</strong> When multiple categories are specified, tools in ANY category are returned:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">GET /api/v1/tools/search?q=summarize&amp;categories=text-processing,productivity</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Returns tools in &quot;text-processing&quot; OR &quot;productivity&quot; category</span><br></span></code></pre></div></div>
<p><strong>Faceted response:</strong> When <code>include_facets=true</code>, the response includes counts for filtering:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;data&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">...</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;meta&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">&quot;page&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">&quot;per_page&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">20</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">&quot;total&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">42</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">&quot;total_pages&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;facets&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;categories&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">&quot;name&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;text-processing&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">&quot;count&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">25</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">&quot;name&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;productivity&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">&quot;count&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">17</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;tags&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">&quot;name&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;ai&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">&quot;count&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">30</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">&quot;name&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;cli&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">&quot;count&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">22</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">&quot;name&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;text&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">&quot;count&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">18</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;owners&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">&quot;name&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;official&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">&quot;count&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">15</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">&quot;name&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;rob&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">&quot;count&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p><strong>Database indexes:</strong></p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Frequent query patterns</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INDEX</span><span class="token plain"> idx_tools_owner_name </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">owner</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INDEX</span><span class="token plain"> idx_tools_owner </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">owner</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- For owner filtering</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INDEX</span><span class="token plain"> idx_tools_category </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">category</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INDEX</span><span class="token plain"> idx_tools_published_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">published_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INDEX</span><span class="token plain"> idx_tools_downloads </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">downloads </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INDEX</span><span class="token plain"> idx_tools_owner_name_version </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">owner</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> version</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- For pagination stability</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INDEX</span><span class="token plain"> idx_tools_sort_stable </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">downloads </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> published_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DESC</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Publisher lookups</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INDEX</span><span class="token plain"> idx_publishers_slug </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> publishers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">slug</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INDEX</span><span class="token plain"> idx_publishers_email </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> publishers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">email</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Token lookups</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INDEX</span><span class="token plain"> idx_api_tokens_hash </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> api_tokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">token_hash</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INDEX</span><span class="token plain"> idx_api_tokens_publisher </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> api_tokens</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">publisher_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="api-version-compatibility">API Version Compatibility<a href="#api-version-compatibility" class="hash-link" aria-label="Direct link to API Version Compatibility" title="Direct link to API Version Compatibility" translate="no"></a></h3>
<p><strong>Forward compatibility:</strong> Clients should ignore unknown fields in API responses:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Good: ignore unknown fields</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">tool </span><span class="token operator">=</span><span class="token plain"> response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;data&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">name </span><span class="token operator">=</span><span class="token plain"> tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;name&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Don&#x27;t fail if &#x27;new_field&#x27; exists but client doesn&#x27;t know about it</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Bad: strict parsing that fails on unknown fields</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">tool </span><span class="token operator">=</span><span class="token plain"> ToolSchema</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">parse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;data&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># May fail on new fields</span><br></span></code></pre></div></div>
<p><strong>Backward compatibility:</strong> The API will:</p>
<ul>
<li class="">Never remove fields in a version (only deprecate)</li>
<li class="">Never change field types</li>
<li class="">Add new optional fields without version bump</li>
<li class="">Use new version (<code>/api/v2</code>) for breaking changes</li>
</ul>
<p><strong>Deprecation process:</strong></p>
<ol>
<li class="">Add <code>X-Deprecated-Field: old_field</code> header</li>
<li class="">Document in changelog</li>
<li class="">Remove after 6 months minimum</li>
<li class="">Major version bump if widely used</li>
</ol>
<p><strong>Client version header:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">X-CmdForge-Client: cli/1.2.0</span><br></span></code></pre></div></div>
<p>Helps server track client versions for deprecation decisions.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="source-of-truth">Source of Truth<a href="#source-of-truth" class="hash-link" aria-label="Direct link to Source of Truth" title="Direct link to Source of Truth" translate="no"></a></h2>
<ul>
<li class="">Gitea registry repo is the source of truth.</li>
<li class="">API syncs repo content into SQLite for fast queries, stats, and FTS5 search.</li>
<li class=""><code>index.json</code> remains useful for offline CLI search and as a fallback.</li>
</ul>
<p>If the cache is stale, the API can fall back to repo reads; a warning header may be emitted.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="namespacing-and-paths">Namespacing and Paths<a href="#namespacing-and-paths" class="hash-link" aria-label="Direct link to Namespacing and Paths" title="Direct link to Namespacing and Paths" translate="no"></a></h2>
<p>Support owner/name from day one:</p>
<ul>
<li class="">Registry path: <code>tools/{owner}/{name}/config.yaml</code></li>
<li class="">API URL: <code>/tools/{owner}/{name}</code></li>
<li class="">Install: <code>cmdforge registry install rob/summarize</code></li>
<li class="">Shorthand: <code>cmdforge registry install summarize</code> resolves to the official namespace.</li>
</ul>
<p>PR branches: <code>submit/{owner}/{name}/{version}</code>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="namespace-identity">Namespace Identity<a href="#namespace-identity" class="hash-link" aria-label="Direct link to Namespace Identity" title="Direct link to Namespace Identity" translate="no"></a></h3>
<p>The <code>owner</code> is an <strong>immutable slug</strong>, not the display name:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- In publishers table</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">slug </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">UNIQUE</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- immutable: &quot;rob&quot;, &quot;alice-dev&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">display_name </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- mutable: &quot;Rob&quot;, &quot;Alice Developer&quot;</span><br></span></code></pre></div></div>
<p><strong>Slug rules:</strong></p>
<ul>
<li class="">Lowercase alphanumeric + hyphens only: <code>^[a-z0-9][a-z0-9-]*[a-z0-9]$</code></li>
<li class="">2-39 characters</li>
<li class="">Cannot start/end with hyphen</li>
<li class="">Set once at registration, cannot be changed</li>
<li class="">Reserved slugs: <code>official</code>, <code>admin</code>, <code>system</code>, <code>api</code>, <code>registry</code></li>
</ul>
<p><strong>Rename policy:</strong></p>
<ul>
<li class=""><code>display_name</code> can be changed anytime via dashboard</li>
<li class=""><code>slug</code> (owner) is permanent to preserve URLs and tool references</li>
<li class="">If a publisher absolutely must change slug (legal reasons, etc.):<!-- -->
<ol>
<li class="">Create new account with new slug</li>
<li class="">Republish tools under new namespace</li>
<li class="">Mark old tools as deprecated with <code>replacement</code> pointing to new namespace</li>
<li class="">Old namespace remains reserved (cannot be reused by others)</li>
</ol>
</li>
</ul>
<p><strong>Why immutable:</strong></p>
<ul>
<li class=""><code>rob/summarize@1.0.0</code> must always resolve to the same tool</li>
<li class="">Prevents namespace hijacking after rename</li>
<li class="">Simplifies caching and CDN strategies</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tool-format-registry--local">Tool Format (Registry == Local)<a href="#tool-format-registry--local" class="hash-link" aria-label="Direct link to Tool Format (Registry == Local)" title="Direct link to Tool Format (Registry == Local)" translate="no"></a></h2>
<p>Registry tool folders mirror local tools:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">tools/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> rob/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> summarize/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> config.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> README.md</span><br></span></code></pre></div></div>
<p>Tool files match the existing CmdForge format. Registry-specific metadata is kept under <code>registry:</code>. Deprecation is tool-defined and top-level:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> summarize</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">version</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;1.2.0&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">deprecated</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token boolean important">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">deprecated_message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Security issue. Use v1.2.1&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">replacement</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;rob/summarize@1.2.1&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">registry</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">published_at</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;2025-01-15T10:30:00Z&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">downloads</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">142</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="attribution-and-source-fields">Attribution and Source Fields<a href="#attribution-and-source-fields" class="hash-link" aria-label="Direct link to Attribution and Source Fields" title="Direct link to Attribution and Source Fields" translate="no"></a></h3>
<p>Tools can include optional source attribution for provenance and licensing:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> summarize</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">version</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;1.2.0&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Summarize text using AI&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Attribution fields (optional)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">source</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> original </span><span class="token comment" style="color:rgb(98, 114, 164)"># original, adapted, or imported</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">license</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> MIT </span><span class="token comment" style="color:rgb(98, 114, 164)"># SPDX license identifier</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> https</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">//example.com/tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">repo</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">author</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Original Author&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># For adapted/imported tools</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">original_tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> other/original</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">summarize@1.0.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">changes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Added French language support&quot;</span><br></span></code></pre></div></div>
<p><strong>Source types:</strong></p>
<table><thead><tr><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><code>original</code></td><td>Created from scratch by the publisher</td></tr><tr><td><code>adapted</code></td><td>Based on another tool with modifications</td></tr><tr><td><code>imported</code></td><td>Direct import of external tool (e.g., from npm/pip)</td></tr></tbody></table>
<p><strong>License field:</strong></p>
<ul>
<li class="">Uses SPDX identifiers: <code>MIT</code>, <code>Apache-2.0</code>, <code>GPL-3.0</code>, etc.</li>
<li class="">Required for registry publication</li>
<li class="">Validated against SPDX license list</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="collections">Collections<a href="#collections" class="hash-link" aria-label="Direct link to Collections" title="Direct link to Collections" translate="no"></a></h2>
<p>Collections are curated groups of tools that can be installed together with a single command.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="collection-structure">Collection Structure<a href="#collection-structure" class="hash-link" aria-label="Direct link to Collection Structure" title="Direct link to Collection Structure" translate="no"></a></h3>
<p>Collections are defined in <code>collections/{name}.yaml</code>:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">processing</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">essentials</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">display_name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Text Processing Essentials&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Essential tools for text processing and manipulation&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">icon</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;📝&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> official/summarize</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> official/translate</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> official/fix</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">grammar</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> official/simplify</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> official/tone</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">shift</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Optional</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">curator</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> official</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">tags</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;text&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;nlp&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;writing&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="collections-api">Collections API<a href="#collections-api" class="hash-link" aria-label="Direct link to Collections API" title="Direct link to Collections API" translate="no"></a></h3>
<p><strong>List all collections:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">GET /api/v1/collections</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Response:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">{</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;data&quot;: [</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;name&quot;: &quot;text-processing-essentials&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;display_name&quot;: &quot;Text Processing Essentials&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;description&quot;: &quot;Essential tools for text processing...&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;icon&quot;: &quot;📝&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;tool_count&quot;: 5,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;curator&quot;: &quot;official&quot;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> ],</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;meta&quot;: {&quot;page&quot;: 1, &quot;per_page&quot;: 20, &quot;total&quot;: 8}</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">}</span><br></span></code></pre></div></div>
<p><strong>Get collection details:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">GET /api/v1/collections/{name}</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Response:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">{</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;data&quot;: {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;name&quot;: &quot;text-processing-essentials&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;display_name&quot;: &quot;Text Processing Essentials&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;description&quot;: &quot;Essential tools for text processing...&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;icon&quot;: &quot;📝&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;curator&quot;: &quot;official&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;tools&quot;: [</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> {&quot;owner&quot;: &quot;official&quot;, &quot;name&quot;: &quot;summarize&quot;, &quot;version&quot;: &quot;1.2.0&quot;, ...},</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> {&quot;owner&quot;: &quot;official&quot;, &quot;name&quot;: &quot;translate&quot;, &quot;version&quot;: &quot;2.1.0&quot;, ...}</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> ]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cli-commands">CLI Commands<a href="#cli-commands" class="hash-link" aria-label="Direct link to CLI Commands" title="Direct link to CLI Commands" translate="no"></a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain"># List available collections</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">cmdforge collections list</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># List in JSON format</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">cmdforge collections list --json</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># View collection details</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">cmdforge collections info text-processing-essentials</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># View in JSON format</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">cmdforge collections info text-processing-essentials --json</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Install all tools in a collection</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">cmdforge collections install text-processing-essentials</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Install with pinned versions from collection</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">cmdforge collections install text-processing-essentials --pinned</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="admin-collections-api">Admin Collections API<a href="#admin-collections-api" class="hash-link" aria-label="Direct link to Admin Collections API" title="Direct link to Admin Collections API" translate="no"></a></h3>
<p>Collections are managed via the admin dashboard at <code>/dashboard/admin/collections</code>:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">GET /api/v1/admin/collections # List all collections (admin)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">POST /api/v1/admin/collections # Create collection (admin)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">PUT /api/v1/admin/collections/:name # Update collection (admin)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">DELETE /api/v1/admin/collections/:name # Delete collection (admin)</span><br></span></code></pre></div></div>
<p><strong>Schema compatibility note:</strong> The current CmdForge config parser may reject unknown top-level keys like <code>deprecated</code>, <code>replacement</code>, and <code>registry</code>. Before implementing registry features:</p>
<ol>
<li class="">Update the YAML parser to ignore unknown keys (permissive mode)</li>
<li class="">Or explicitly define these fields in the Tool dataclass with defaults</li>
<li class="">Validate registry-specific fields only when publishing, not when running locally</li>
</ol>
<p>This ensures local tools continue to work even if they don&#x27;t have registry fields.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="versioning-and-immutability">Versioning and Immutability<a href="#versioning-and-immutability" class="hash-link" aria-label="Direct link to Versioning and Immutability" title="Direct link to Versioning and Immutability" translate="no"></a></h2>
<ul>
<li class="">Unique key: <code>owner/name + version</code>.</li>
<li class="">Published versions are immutable.</li>
<li class="">Deprecation uses <code>deprecated</code>, <code>deprecated_message</code>, and <code>replacement</code>.</li>
<li class="">CLI warns on install if a version is deprecated.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="yank-policy">Yank Policy<a href="#yank-policy" class="hash-link" aria-label="Direct link to Yank Policy" title="Direct link to Yank Policy" translate="no"></a></h3>
<p>Yanking allows removing a version from resolution without deleting it (for auditability):</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># In tool config</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">yanked</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token boolean important">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">yanked_reason</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Critical security vulnerability CVE-2025-1234&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">yanked_at</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;2025-01-20T15:00:00Z&quot;</span><br></span></code></pre></div></div>
<p><strong>Yanked version behavior:</strong></p>
<table><thead><tr><th>Operation</th><th>Behavior</th></tr></thead><tbody><tr><td><code>install foo@1.0.0</code> (exact)</td><td>Warns but allows install</td></tr><tr><td><code>install foo@^1.0.0</code> (constraint)</td><td>Excludes yanked, resolves to next valid</td></tr><tr><td><code>search</code> / <code>browse</code></td><td>Hidden by default, shown with <code>--include-yanked</code></td></tr><tr><td>Direct URL access</td><td>Returns tool with <code>yanked: true</code> in response</td></tr><tr><td>Already installed</td><td>Continues to work, no forced removal</td></tr></tbody></table>
<p><strong>Database schema addition:</strong></p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Add to tools table</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">yanked </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BOOLEAN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token boolean">FALSE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">yanked_reason </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">yanked_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><br></span></code></pre></div></div>
<p><strong>Yank vs Delete:</strong></p>
<ul>
<li class=""><strong>Yank</strong>: Version remains in DB, excluded from resolution, auditable</li>
<li class=""><strong>Delete</strong>: Reserved for DMCA/legal, requires admin action, leaves tombstone record</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="version-format">Version Format<a href="#version-format" class="hash-link" aria-label="Direct link to Version Format" title="Direct link to Version Format" translate="no"></a></h3>
<p>Tools use semantic versioning (semver):</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Examples:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> 1.0.0 # stable release</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> 1.2.3 # stable release</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> 2.0.0-alpha.1 # prerelease</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> 2.0.0-beta.2 # prerelease</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> 2.0.0-rc.1 # release candidate</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="version-constraints">Version Constraints<a href="#version-constraints" class="hash-link" aria-label="Direct link to Version Constraints" title="Direct link to Version Constraints" translate="no"></a></h3>
<p>Manifest files support these constraint formats:</p>
<table><thead><tr><th>Constraint</th><th>Meaning</th><th>Example Match</th></tr></thead><tbody><tr><td><code>1.2.3</code></td><td>Exact version</td><td><code>1.2.3</code> only</td></tr><tr><td><code>&gt;=1.2.0</code></td><td>Minimum version</td><td><code>1.2.0</code>, <code>1.3.0</code>, <code>2.0.0</code></td></tr><tr><td><code>&lt;2.0.0</code></td><td>Below version</td><td><code>1.9.9</code>, <code>1.0.0</code></td></tr><tr><td><code>&gt;=1.0.0,&lt;2.0.0</code></td><td>Range</td><td><code>1.0.0</code> to <code>1.9.9</code></td></tr><tr><td><code>^1.2.3</code></td><td>Compatible (same major)</td><td><code>1.2.3</code> to <code>1.9.9</code></td></tr><tr><td><code>~1.2.3</code></td><td>Approximately (same minor)</td><td><code>1.2.3</code> to <code>1.2.9</code></td></tr><tr><td><code>*</code></td><td>Any version</td><td>latest stable</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="version-resolution-rules">Version Resolution Rules<a href="#version-resolution-rules" class="hash-link" aria-label="Direct link to Version Resolution Rules" title="Direct link to Version Resolution Rules" translate="no"></a></h3>
<p>When resolving a version constraint:</p>
<ol>
<li class=""><strong>Filter</strong>: Get all versions matching the constraint</li>
<li class=""><strong>Exclude prereleases</strong>: Unless constraint explicitly includes them (e.g., <code>&gt;=2.0.0-alpha.1</code>)</li>
<li class=""><strong>Sort</strong>: By semver precedence (descending)</li>
<li class=""><strong>Select</strong>: Highest matching version</li>
</ol>
<p><strong>Tie-breakers:</strong></p>
<ul>
<li class="">Stable versions preferred over prereleases</li>
<li class="">Later publish date wins if versions are equal (shouldn&#x27;t happen with immutability)</li>
</ul>
<p><strong>Unsatisfiable constraints:</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// API Response: 404</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;error&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;code&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;VERSION_NOT_FOUND&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;message&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;No version of &#x27;rob/summarize&#x27; satisfies constraint &#x27;&gt;=5.0.0&#x27;&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;details&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;tool&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;rob/summarize&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;constraint&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;&gt;=5.0.0&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;available_versions&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;1.0.0&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;1.1.0&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;1.2.0&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;latest_stable&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;1.2.0&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="prerelease-handling">Prerelease Handling<a href="#prerelease-handling" class="hash-link" aria-label="Direct link to Prerelease Handling" title="Direct link to Prerelease Handling" translate="no"></a></h3>
<ul>
<li class="">Prereleases are <strong>not</strong> returned for <code>*</code> or range constraints by default</li>
<li class="">To install prerelease: <code>cmdforge registry install rob/summarize@2.0.0-beta.1</code></li>
<li class="">To allow prereleases in manifest: <code>version: &quot;&gt;=2.0.0-0&quot;</code> (the <code>-0</code> suffix includes prereleases)</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="download-endpoint-version-selection">Download Endpoint Version Selection<a href="#download-endpoint-version-selection" class="hash-link" aria-label="Direct link to Download Endpoint Version Selection" title="Direct link to Download Endpoint Version Selection" translate="no"></a></h3>
<p>The <code>/api/v1/tools/{owner}/{name}/download</code> endpoint accepts version parameters:</p>
<table><thead><tr><th>Parameter</th><th>Behavior</th><th>Example</th></tr></thead><tbody><tr><td>(none)</td><td>Returns latest stable version</td><td><code>/download</code><code>1.2.0</code></td></tr><tr><td><code>version=1.2.0</code></td><td>Exact version (must exist)</td><td><code>/download?version=1.2.0</code></td></tr><tr><td><code>version=^1.0.0</code></td><td>Server resolves constraint</td><td><code>/download?version=^1.0.0</code><code>1.2.0</code></td></tr><tr><td><code>version=latest</code></td><td>Alias for latest stable</td><td><code>/download?version=latest</code></td></tr></tbody></table>
<p><strong>Server-side resolution:</strong> The API server resolves version constraints, not the client. This ensures consistent resolution and allows the server to apply policies (e.g., exclude yanked versions).</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">GET /api/v1/tools/rob/summarize/download?version=^1.0.0&amp;install=true</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Response (200):</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">{</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;data&quot;: {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;owner&quot;: &quot;rob&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;name&quot;: &quot;summarize&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;resolved_version&quot;: &quot;1.2.0&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;config&quot;: &quot;... YAML content ...&quot;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> },</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;meta&quot;: {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;constraint&quot;: &quot;^1.0.0&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;available_versions&quot;: [&quot;1.0.0&quot;, &quot;1.1.0&quot;, &quot;1.2.0&quot;]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">}</span><br></span></code></pre></div></div>
<p><strong>Invalid/unsatisfiable constraint:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">GET /api/v1/tools/rob/summarize/download?version=^5.0.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Response (404):</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">{</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;error&quot;: {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;code&quot;: &quot;CONSTRAINT_UNSATISFIABLE&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;message&quot;: &quot;No version matches constraint &#x27;^5.0.0&#x27;&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;details&quot;: {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;constraint&quot;: &quot;^5.0.0&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;latest_stable&quot;: &quot;1.2.0&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;available_versions&quot;: [&quot;1.0.0&quot;, &quot;1.1.0&quot;, &quot;1.2.0&quot;]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tool-resolution-order">Tool Resolution Order<a href="#tool-resolution-order" class="hash-link" aria-label="Direct link to Tool Resolution Order" title="Direct link to Tool Resolution Order" translate="no"></a></h2>
<p>When a tool is invoked, the CLI searches in this order:</p>
<ol>
<li class=""><strong>Local project</strong>: <code>./.cmdforge/&lt;owner&gt;/&lt;name&gt;/config.yaml</code> (or <code>./.cmdforge/&lt;name&gt;/</code> for unnamespaced)</li>
<li class=""><strong>Global user</strong>: <code>~/.cmdforge/&lt;owner&gt;/&lt;name&gt;/config.yaml</code></li>
<li class=""><strong>Registry</strong>: Fetch from API, install to global, then run</li>
<li class=""><strong>Error</strong>: <code>Tool &#x27;&lt;toolname&gt;&#x27; not found</code></li>
</ol>
<p>Step 3 only occurs if <code>auto_fetch_from_registry: true</code> in config (default: true).</p>
<p><strong>Path convention:</strong> Use <code>.cmdforge/</code> (with leading dot) for both local and global to maintain consistency.</p>
<p>Resolution also respects namespacing:</p>
<ul>
<li class=""><code>summarize</code> → searches for any tool named <code>summarize</code>, prefers <code>official/summarize</code> if exists</li>
<li class=""><code>rob/summarize</code> → searches for exactly <code>rob/summarize</code></li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="official-namespace">Official Namespace<a href="#official-namespace" class="hash-link" aria-label="Direct link to Official Namespace" title="Direct link to Official Namespace" translate="no"></a></h3>
<p>The slug <code>official</code> is reserved for curated, high-quality tools maintained by the registry administrators.</p>
<ul>
<li class="">Shorthand <code>summarize</code> resolves to <code>official/summarize</code> if it exists</li>
<li class="">If no <code>official/summarize</code>, falls back to most-downloaded tool named <code>summarize</code></li>
<li class="">To avoid ambiguity, always use full <code>owner/name</code> in manifests</li>
</ul>
<p>Reserved slugs that cannot be registered: <code>official</code>, <code>admin</code>, <code>system</code>, <code>api</code>, <code>registry</code>, <code>cmdforge</code></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="auto-fetch-behavior">Auto-Fetch Behavior<a href="#auto-fetch-behavior" class="hash-link" aria-label="Direct link to Auto-Fetch Behavior" title="Direct link to Auto-Fetch Behavior" translate="no"></a></h2>
<p>When enabled (<code>auto_fetch_from_registry: true</code>), missing tools are automatically fetched:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ summarize &lt; file.txt</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Tool &#x27;summarize&#x27; not found locally.</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Fetching from registry...</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Installed: official/summarize@1.2.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Running...</span><br></span></code></pre></div></div>
<p>Behavior details:</p>
<ul>
<li class="">Fetches latest stable version unless pinned in <code>cmdforge.yaml</code></li>
<li class="">Installs to <code>~/.cmdforge/&lt;owner&gt;/&lt;name&gt;/</code></li>
<li class="">Generates wrapper script in <code>~/.local/bin/</code></li>
<li class="">Subsequent runs use local copy (no re-fetch)</li>
</ul>
<p>To disable (require explicit install):</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># ~/.cmdforge/config.yaml</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">auto_fetch_from_registry</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token boolean important">false</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrapper-script-collisions">Wrapper Script Collisions<a href="#wrapper-script-collisions" class="hash-link" aria-label="Direct link to Wrapper Script Collisions" title="Direct link to Wrapper Script Collisions" translate="no"></a></h3>
<p>When two tools from different owners have the same name:</p>
<table><thead><tr><th>Scenario</th><th>Behavior</th></tr></thead><tbody><tr><td>Install <code>official/summarize</code></td><td>Creates wrapper <code>~/.local/bin/summarize</code></td></tr><tr><td>Install <code>rob/summarize</code> (collision)</td><td>Creates wrapper <code>~/.local/bin/rob-summarize</code></td></tr><tr><td>Uninstall <code>official/summarize</code></td><td>Removes <code>summarize</code> wrapper, promotes <code>rob-summarize</code><code>summarize</code> if desired</td></tr></tbody></table>
<p>The first-installed tool with a given name gets the short wrapper. Subsequent tools use <code>owner-name</code> format.</p>
<p>To invoke a specific owner&#x27;s tool:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Short form (whichever was installed first)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">summarize &lt; file.txt</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Explicit owner form (always works)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">rob-summarize &lt; file.txt</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Or via cmdforge run</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">cmdforge run rob/summarize &lt; file.txt</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="project-manifest-cmdforgeyaml">Project Manifest (cmdforge.yaml)<a href="#project-manifest-cmdforgeyaml" class="hash-link" aria-label="Direct link to Project Manifest (cmdforge.yaml)" title="Direct link to Project Manifest (cmdforge.yaml)" translate="no"></a></h2>
<p>Defines tool dependencies with optional runtime overrides:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">name: my-ai-project</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">version: &quot;1.0.0&quot;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">dependencies:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> - name: rob/summarize</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> version: &quot;&gt;=1.0.0&quot;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">overrides:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> rob/summarize:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> provider: ollama</span><br></span></code></pre></div></div>
<p>Overrides are applied at runtime and do not mutate installed tool configs.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cli-config-and-tokens">CLI Config and Tokens<a href="#cli-config-and-tokens" class="hash-link" aria-label="Direct link to CLI Config and Tokens" title="Direct link to CLI Config and Tokens" translate="no"></a></h2>
<p>Global config lives in <code>~/.cmdforge/config.yaml</code>:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">registry</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> https</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">//cmdforge.brrd.tech/api/v1 </span><span class="token comment" style="color:rgb(98, 114, 164)"># Must match canonical base path</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">token</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;reg_xxxxxxxxxxxx&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">client_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;anon_abc123def456&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">auto_fetch_from_registry</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token boolean important">true</span><br></span></code></pre></div></div>
<p><code>client_id</code> is generated locally and used for anonymous install dedupe.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="publishing-and-auth">Publishing and Auth<a href="#publishing-and-auth" class="hash-link" aria-label="Direct link to Publishing and Auth" title="Direct link to Publishing and Auth" translate="no"></a></h2>
<p>Publishing uses registry accounts, not Gitea accounts:</p>
<ul>
<li class="">Public endpoints require no auth.</li>
<li class=""><code>POST /tools</code> requires a registry token.</li>
<li class="">The API server uses a private Gitea service account to open PRs.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="publish-idempotency-and-edge-cases">Publish Idempotency and Edge Cases<a href="#publish-idempotency-and-edge-cases" class="hash-link" aria-label="Direct link to Publish Idempotency and Edge Cases" title="Direct link to Publish Idempotency and Edge Cases" translate="no"></a></h3>
<p><strong>Idempotency key:</strong> <code>owner/name@version</code></p>
<table><thead><tr><th>Scenario</th><th>API Response</th><th>HTTP Code</th></tr></thead><tbody><tr><td>New version, no PR exists</td><td>Create PR, return URL</td><td><code>201 Created</code></td></tr><tr><td>PR already exists (pending)</td><td>Return existing PR URL</td><td><code>200 OK</code></td></tr><tr><td>Version already published</td><td>Error: version exists</td><td><code>409 Conflict</code></td></tr><tr><td>PR was closed without merge</td><td>Allow new PR</td><td><code>201 Created</code></td></tr><tr><td>PR was merged, then tool deleted</td><td>Error: version exists (tombstone)</td><td><code>409 Conflict</code></td></tr></tbody></table>
<p><strong>Version immutability enforcement:</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// Attempt to publish existing version</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// Response: 409 Conflict</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;error&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;code&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;VERSION_EXISTS&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;message&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Version 1.2.0 of &#x27;rob/summarize&#x27; already exists and cannot be overwritten&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;details&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;published_at&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;2025-01-15T10:30:00Z&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;action&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Bump version number to publish changes&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p><strong>Closed PR handling:</strong></p>
<ul>
<li class="">Track PR state in database: <code>pending</code>, <code>merged</code>, <code>closed</code></li>
<li class="">If PR was closed (rejected/abandoned), allow new submission for same version</li>
<li class="">If PR was merged, version is immutable forever</li>
</ul>
<p><strong>Update flow (new version, not overwrite):</strong></p>
<ol>
<li class="">Developer modifies tool locally</li>
<li class="">Bumps version in <code>config.yaml</code> (e.g., <code>1.2.0</code><code>1.3.0</code>)</li>
<li class="">Runs <code>cmdforge registry publish</code></li>
<li class="">New PR created for <code>1.3.0</code></li>
<li class="">Old version <code>1.2.0</code> remains available</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="publisher-registration">Publisher Registration<a href="#publisher-registration" class="hash-link" aria-label="Direct link to Publisher Registration" title="Direct link to Publisher Registration" translate="no"></a></h2>
<p>Publishers register on the registry website, not Gitea:</p>
<p><strong>Registration flow:</strong></p>
<ol>
<li class="">User visits <code>https://gitea.brrd.tech/registry/register</code> (or future <code>cmdforge.brrd.tech</code>)</li>
<li class="">Creates account with email + password + slug</li>
<li class="">Receives verification email (optional in v1, but track <code>verified</code> status)</li>
<li class="">Logs into dashboard at <code>/dashboard</code></li>
<li class="">Generates API token from dashboard</li>
<li class="">Uses token in CLI for publishing</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="authentication-security">Authentication Security<a href="#authentication-security" class="hash-link" aria-label="Direct link to Authentication Security" title="Direct link to Authentication Security" translate="no"></a></h3>
<p><strong>Password hashing:</strong></p>
<ul>
<li class="">Algorithm: Argon2id (memory-hard, recommended by OWASP)</li>
<li class="">Parameters: <code>memory=65536, iterations=3, parallelism=4</code></li>
<li class="">Library: <code>argon2-cffi</code> for Python</li>
</ul>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> argon2 </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> PasswordHasher</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">ph </span><span class="token operator">=</span><span class="token plain"> PasswordHasher</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">memory_cost</span><span class="token operator">=</span><span class="token number">65536</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> time_cost</span><span class="token operator">=</span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> parallelism</span><span class="token operator">=</span><span class="token number">4</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin" style="color:rgb(189, 147, 249)">hash</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> ph</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token builtin" style="color:rgb(189, 147, 249)">hash</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">password</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">ph</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">verify</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token builtin" style="color:rgb(189, 147, 249)">hash</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> password</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># raises on mismatch</span><br></span></code></pre></div></div>
<p><strong>API token format:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">reg_&lt;random-32-bytes-base62&gt;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Example: reg_7kX9mPqR2sT4vW6xY8zA1bC3dE5fG7hJ</span><br></span></code></pre></div></div>
<ul>
<li class="">Prefix <code>reg_</code> for easy identification in logs/configs</li>
<li class="">32 bytes of cryptographically random data</li>
<li class="">Base62 encoded (alphanumeric, no special chars)</li>
<li class="">Total length: ~47 characters</li>
<li class="">Stored as SHA-256 hash in database (never plain text)</li>
</ul>
<p><strong>Token lifecycle:</strong></p>
<table><thead><tr><th>Action</th><th>Behavior</th></tr></thead><tbody><tr><td>Generate</td><td>Create new token, return once, store hash</td></tr><tr><td>List</td><td>Show token name, created date, last used (not the token itself)</td></tr><tr><td>Revoke</td><td>Set <code>revoked_at</code> timestamp, reject future uses</td></tr><tr><td>Rotate</td><td>Generate new token, optionally revoke old</td></tr></tbody></table>
<p><strong>Rate limits:</strong></p>
<table><thead><tr><th>Endpoint</th><th>Limit</th><th>Window</th><th>Scope</th><th>Retry-After</th></tr></thead><tbody><tr><td><code>POST /register</code></td><td>5</td><td>1 hour</td><td>IP</td><td>3600</td></tr><tr><td><code>POST /login</code></td><td>10</td><td>15 min</td><td>IP</td><td>900</td></tr><tr><td><code>POST /login</code> (failed)</td><td>5</td><td>15 min</td><td>IP + email</td><td>900</td></tr><tr><td><code>POST /tokens</code></td><td>10</td><td>1 hour</td><td>Token</td><td>3600</td></tr><tr><td><code>POST /tools</code></td><td>20</td><td>1 hour</td><td>Token</td><td>3600</td></tr><tr><td><code>GET /tools/*</code></td><td>100</td><td>1 min</td><td>IP</td><td>60</td></tr><tr><td><code>GET /download</code></td><td>60</td><td>1 min</td><td>IP</td><td>60</td></tr></tbody></table>
<p><strong>Rate limit response (429):</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;error&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;code&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;RATE_LIMITED&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;message&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Too many requests. Try again in 60 seconds.&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;details&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;limit&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">100</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;window&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;1 minute&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;retry_after&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">60</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p><strong>Headers on rate-limited response:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">HTTP/1.1 429 Too Many Requests</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Retry-After: 60</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">X-RateLimit-Limit: 100</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">X-RateLimit-Remaining: 0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">X-RateLimit-Reset: 1705766400</span><br></span></code></pre></div></div>
<p><strong>Scope priority:</strong> For authenticated requests, both IP and token limits apply. The more restrictive limit wins.</p>
<p><strong>Account lockout:</strong></p>
<ul>
<li class="">After 5 failed login attempts: 15-minute lockout for that email</li>
<li class="">After 10 failed attempts: 1-hour lockout</li>
<li class="">Lockout clears on successful password reset</li>
</ul>
<p><strong>Password reset flow (deferred to v1.1):</strong></p>
<ol>
<li class="">User requests reset via email</li>
<li class="">Server generates time-limited token (1 hour expiry)</li>
<li class="">Email contains reset link with token</li>
<li class="">User sets new password</li>
<li class="">All existing sessions/tokens optionally invalidated</li>
</ol>
<p><strong>Email verification flow (deferred to v1.1):</strong></p>
<ol>
<li class="">On registration, send verification email</li>
<li class="">User clicks link with verification token</li>
<li class="">Set <code>verified = true</code> in database</li>
<li class="">Unverified accounts can browse but not publish</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="token-scopes-and-authorization">Token Scopes and Authorization<a href="#token-scopes-and-authorization" class="hash-link" aria-label="Direct link to Token Scopes and Authorization" title="Direct link to Token Scopes and Authorization" translate="no"></a></h3>
<p>Tokens have scopes that limit their capabilities:</p>
<table><thead><tr><th>Scope</th><th>Permissions</th></tr></thead><tbody><tr><td><code>read</code></td><td>View own published tools, download stats</td></tr><tr><td><code>publish</code></td><td>Submit new tools, update own tool metadata</td></tr><tr><td><code>admin</code></td><td>Yank tools, manage categories (registry admins only)</td></tr></tbody></table>
<p><strong>Default scope:</strong> New tokens get <code>read,publish</code> by default.</p>
<p><strong>Ownership enforcement:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">@app</span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">route</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;/api/v1/tools&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> methods</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;POST&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">@require_token</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">scopes</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;publish&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">publish_tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> token </span><span class="token operator">=</span><span class="token plain"> get_current_token</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> tool_data </span><span class="token operator">=</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">json</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Enforce owner == token holder&#x27;s slug</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> tool_data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;owner&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">!=</span><span class="token plain"> token</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">publisher</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">slug</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;error&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;code&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;FORBIDDEN&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;message&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f&quot;Cannot publish to namespace &#x27;</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">tool_data</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string-interpolation interpolation string" style="color:rgb(255, 121, 198)">&#x27;owner&#x27;</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">&#x27;. &quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f&quot;Your namespace is &#x27;</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">token</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token string-interpolation interpolation">publisher</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token string-interpolation interpolation">slug</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">&#x27;.&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">403</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Proceed with publish...</span><br></span></code></pre></div></div>
<p><strong><code>GET /api/v1/me/tools</code> authorization:</strong></p>
<ul>
<li class="">Requires valid token with <code>read</code> scope</li>
<li class="">Returns only tools where <code>owner == token.publisher.slug</code></li>
<li class="">Includes pending PRs and all versions (including yanked)</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="web-session-security">Web Session Security<a href="#web-session-security" class="hash-link" aria-label="Direct link to Web Session Security" title="Direct link to Web Session Security" translate="no"></a></h3>
<p>Dashboard login uses session cookies (not tokens) for browser auth:</p>
<p><strong>Cookie settings:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">SESSION_COOKIE_NAME </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;cmdforge_session&#x27;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">SESSION_COOKIE_HTTPONLY </span><span class="token operator">=</span><span class="token plain"> </span><span class="token boolean">True</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Prevent JS access</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">SESSION_COOKIE_SECURE </span><span class="token operator">=</span><span class="token plain"> </span><span class="token boolean">True</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># HTTPS only in production</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">SESSION_COOKIE_SAMESITE </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;Lax&#x27;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># CSRF protection</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">SESSION_COOKIE_MAX_AGE </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">86400</span><span class="token plain"> </span><span class="token operator">*</span><span class="token plain"> </span><span class="token number">7</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># 7 days</span><br></span></code></pre></div></div>
<p><strong>CSRF protection:</strong></p>
<ul>
<li class="">All POST/PUT/DELETE forms include <code>csrf_token</code> hidden field</li>
<li class="">Token validated server-side before processing</li>
<li class="">403 Forbidden if token missing or invalid</li>
</ul>
<p><strong>Session lifecycle:</strong></p>
<table><thead><tr><th>Event</th><th>Action</th></tr></thead><tbody><tr><td>Login</td><td>Create session, set cookie</td></tr><tr><td>Logout</td><td>Delete session, clear cookie</td></tr><tr><td>Idle 24h</td><td>Session expires, re-login required</td></tr><tr><td>Password change</td><td>Invalidate all sessions</td></tr><tr><td>Token revocation</td><td>Existing sessions continue (token != session)</td></tr></tbody></table>
<p><strong>Secure session storage:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Store sessions in DB, not filesystem</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> flask_session </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> Session</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">config</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;SESSION_TYPE&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;sqlalchemy&#x27;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">config</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;SESSION_SQLALCHEMY_TABLE&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;sessions&#x27;</span><br></span></code></pre></div></div>
<p><strong>Database schema:</strong></p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Publishers</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> publishers </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">KEY</span><span class="token plain"> AUTOINCREMENT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> email </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">UNIQUE</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> password_hash </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> slug </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">UNIQUE</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- immutable namespace: &quot;rob&quot;, &quot;alice-dev&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> display_name </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- mutable: &quot;Rob&quot;, &quot;Alice Developer&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> bio </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> website </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> verified </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BOOLEAN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token boolean">FALSE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> locked_until </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- account lockout</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> failed_login_attempts </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> created_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CURRENT_TIMESTAMP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> updated_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CURRENT_TIMESTAMP</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- API tokens (one publisher can have multiple)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> api_tokens </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">KEY</span><span class="token plain"> AUTOINCREMENT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> publisher_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">REFERENCES</span><span class="token plain"> publishers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> token_hash </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> name </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- &quot;CLI token&quot;, &quot;CI token&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> last_used_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> created_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CURRENT_TIMESTAMP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> revoked_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- NULL if active</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Tools (links to publisher)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> tools </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">KEY</span><span class="token plain"> AUTOINCREMENT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> owner </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- namespace slug (immutable, from publisher.slug)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> name </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> version </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> description </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> category </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> tags </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- JSON array</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> config_yaml </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- Full tool config</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> readme </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> publisher_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">REFERENCES</span><span class="token plain"> publishers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> deprecated </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BOOLEAN</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token boolean">FALSE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> deprecated_message </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> replacement </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> downloads </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> published_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CURRENT_TIMESTAMP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">UNIQUE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">owner</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> version</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Download stats (for deduplication)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> download_stats </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">KEY</span><span class="token plain"> AUTOINCREMENT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> tool_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">REFERENCES</span><span class="token plain"> tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> client_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> downloaded_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DATE</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">UNIQUE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tool_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> client_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> downloaded_at</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Search index (FTS5)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> VIRTUAL </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> tools_fts </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">USING</span><span class="token plain"> fts5</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tags</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> readme</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> content</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;tools&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> content_rowid</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;id&#x27;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- FTS5 sync triggers (required for external content tables)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TRIGGER</span><span class="token plain"> tools_ai </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AFTER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> tools </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BEGIN</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTO</span><span class="token plain"> tools_fts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">rowid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tags</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> readme</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VALUES</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">new</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> new</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> new</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> new</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tags</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> new</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">readme</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TRIGGER</span><span class="token plain"> tools_ad </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AFTER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DELETE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> tools </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BEGIN</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTO</span><span class="token plain"> tools_fts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tools_fts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> rowid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tags</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> readme</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VALUES</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;delete&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> old</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> old</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> old</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> old</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tags</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> old</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">readme</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TRIGGER</span><span class="token plain"> tools_au </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">AFTER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">UPDATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">ON</span><span class="token plain"> tools </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">BEGIN</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTO</span><span class="token plain"> tools_fts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tools_fts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> rowid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tags</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> readme</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VALUES</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;delete&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> old</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> old</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> old</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> old</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tags</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> old</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">readme</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INSERT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTO</span><span class="token plain"> tools_fts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">rowid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tags</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> readme</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">VALUES</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">new</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> new</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> new</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> new</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">tags</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> new</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">readme</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">END</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Pending PRs (track publish state)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> pending_prs </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">KEY</span><span class="token plain"> AUTOINCREMENT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> publisher_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">REFERENCES</span><span class="token plain"> publishers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> owner </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> name </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> version </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> pr_number </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> pr_url </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">status</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;pending&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- pending, merged, closed</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> created_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CURRENT_TIMESTAMP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> updated_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CURRENT_TIMESTAMP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">UNIQUE</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">owner</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> version</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- Webhook sync log (idempotency)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> webhook_log </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">KEY</span><span class="token plain"> AUTOINCREMENT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> delivery_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">UNIQUE</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- Gitea delivery ID</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> event_type </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> processed_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CURRENT_TIMESTAMP</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p><strong>Note on tags indexing:</strong> The <code>tags</code> column stores JSON arrays as text. For v1, FTS5 will search within the JSON string. If tag filtering becomes a bottleneck, normalize to a <code>tool_tags</code> junction table:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- Future: normalized tags (if needed)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> tags </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">KEY</span><span class="token plain"> AUTOINCREMENT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> name </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">UNIQUE</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> tool_tags </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> tool_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">REFERENCES</span><span class="token plain"> tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> tag_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">REFERENCES</span><span class="token plain"> tags</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">KEY</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tool_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tag_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p><strong>Connecting to your account:</strong></p>
<p>The recommended way to authenticate is using the app pairing flow, which eliminates manual token copying:</p>
<p><strong>CLI connection flow:</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge config connect rob</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Connecting to CmdForge as @rob...</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Device: my-laptop</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Waiting for approval from the web interface...</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Go to https://cmdforge.brrd.tech/dashboard/connections</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">and click &#x27;Connect New App&#x27;, then &#x27;I&#x27;ve Run the Command&#x27;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Press Ctrl+C to cancel</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Waiting...</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Connected successfully!</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Your device &#x27;my-laptop&#x27; is now linked to @rob</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">You can now publish tools with: cmdforge registry publish</span><br></span></code></pre></div></div>
<p><strong>TUI connection flow:</strong></p>
<p>The TUI (<code>cmdforge ui</code>) includes a Connect button for connecting without using the command line:</p>
<ol>
<li class="">Click &quot;Connect&quot; button in the main menu</li>
<li class="">Enter your CmdForge username (create account at cmdforge.brrd.tech if needed)</li>
<li class="">A countdown timer shows the pairing expiration (5 minutes)</li>
<li class="">Go to cmdforge.brrd.tech/dashboard/connections in your browser</li>
<li class="">Click &quot;Connect New App&quot; and approve the pending connection</li>
<li class="">TUI automatically detects approval and saves the token</li>
</ol>
<p><strong>CLI first-time publish flow:</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge registry publish</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">No registry account configured.</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Options:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">1. Connect to account (recommended): cmdforge config connect &lt;username&gt;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">2. Manual token entry</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Choose option [1]: 1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Enter username: rob</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Connecting to CmdForge as @rob...</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">[Follows connection flow above]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Validating tool...</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">✓ config.yaml is valid</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">✓ README.md exists (2.3 KB)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">✓ Version 1.0.0 not yet published</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Publishing rob/my-tool@1.0.0...</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">✓ PR created: https://gitea.brrd.tech/rob/CmdForge-Registry/pulls/42</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Your tool is pending review. You&#x27;ll receive an email when it&#x27;s approved.</span><br></span></code></pre></div></div>
<p><strong>TUI publish flow:</strong></p>
<p>When already connected, the TUI main menu shows &quot;Publish&quot; instead of &quot;Connect&quot;:</p>
<ol>
<li class="">Select a tool in the list</li>
<li class="">Click &quot;Publish&quot; button</li>
<li class="">If no version in config, TUI prompts for version number</li>
<li class="">Confirm and publish</li>
</ol>
<p><strong>Private sync after save:</strong></p>
<p>When connected to an account, saving a tool in the TUI offers to sync it privately to the registry:</p>
<ol>
<li class="">Save a tool (new or edited)</li>
<li class="">TUI asks: &quot;Sync to registry privately?&quot;</li>
<li class="">If yes, enter/confirm version number</li>
<li class="">Tool is published with <code>visibility: private</code> (only you can see it)</li>
<li class="">Useful for backup or accessing your tools from multiple machines</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cli-commands-reference">CLI Commands Reference<a href="#cli-commands-reference" class="hash-link" aria-label="Direct link to CLI Commands Reference" title="Direct link to CLI Commands Reference" translate="no"></a></h2>
<p>Full mapping of CLI commands to API calls:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="registry-commands">Registry Commands<a href="#registry-commands" class="hash-link" aria-label="Direct link to Registry Commands" title="Direct link to Registry Commands" translate="no"></a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Search for tools (basic)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge registry search &lt;query&gt; [--category=&lt;cat&gt;] [--limit=20]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → GET /api/v1/tools/search?q=&lt;query&gt;&amp;category=&lt;cat&gt;&amp;limit=20</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Search with advanced filtering</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge registry search &lt;query&gt; [options]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Options:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> -c, --category CAT Filter by category</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> -t, --tag TAG Filter by tag (repeatable, AND logic)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> -o, --owner OWNER Filter by publisher/owner</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> --min-downloads N Minimum downloads</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> --popular Shortcut for --min-downloads 100</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> --new Shortcut for --max-downloads 10</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> --since DATE Published after (YYYY-MM-DD)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> --before DATE Published before (YYYY-MM-DD)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> -s, --sort FIELD Sort by: relevance, downloads, published_at, name</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> -l, --limit N Max results (default: 20)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> --json Output as JSON</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> --show-facets Show category/tag counts</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> --deprecated Include deprecated tools</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># List available tags</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge registry tags [-c CATEGORY] [-l LIMIT] [--json]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → GET /api/v1/tags?category=&lt;cat&gt;&amp;limit=&lt;limit&gt;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Browse tools (TUI)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge registry browse [--category=&lt;cat&gt;]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → GET /api/v1/tools?category=&lt;cat&gt;&amp;page=1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → GET /api/v1/categories</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># View tool details</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge registry info &lt;owner/name&gt;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → GET /api/v1/tools/&lt;owner&gt;/&lt;name&gt;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Install a tool</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge registry install &lt;owner/name&gt; [--version=&lt;ver&gt;]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → GET /api/v1/tools/&lt;owner&gt;/&lt;name&gt;/download?version=&lt;ver&gt;&amp;install=true</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → Writes to ~/.cmdforge/&lt;owner&gt;/&lt;name&gt;/config.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → Generates ~/.local/bin/&lt;name&gt; wrapper (or &lt;owner&gt;-&lt;name&gt; if collision)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Uninstall a tool</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge registry uninstall &lt;owner/name&gt;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → Removes ~/.cmdforge/&lt;owner&gt;/&lt;name&gt;/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → Removes wrapper script</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Publish a tool</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge registry publish [path] [--dry-run]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → POST /api/v1/tools (with registry token)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → Returns PR URL</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># List my published tools</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge registry my-tools</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → GET /api/v1/me/tools (with registry token)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Update index cache</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge registry update</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → GET /api/v1/index.json</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → Writes to ~/.cmdforge/registry/index.json</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="project-commands">Project Commands<a href="#project-commands" class="hash-link" aria-label="Direct link to Project Commands" title="Direct link to Project Commands" translate="no"></a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Install project dependencies from cmdforge.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge install</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → Reads ./cmdforge.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → For each dependency:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> GET /api/v1/tools/&lt;owner&gt;/&lt;name&gt;/download?version=&lt;constraint&gt;&amp;install=true</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → Installs to ~/.cmdforge/&lt;owner&gt;/&lt;name&gt;/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Add a dependency to cmdforge.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge add &lt;owner/name&gt; [--version=&lt;constraint&gt;]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → Adds to ./cmdforge.yaml dependencies</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → Runs install for that tool</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Show project dependencies status</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge deps</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → Reads ./cmdforge.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → Shows installed status for each dependency</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → Note: &quot;cmdforge list&quot; is reserved for listing installed tools</span><br></span></code></pre></div></div>
<p><strong>Command naming note:</strong> <code>cmdforge list</code> already exists to list locally installed tools. Use <code>cmdforge deps</code> to show project manifest dependencies.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="config-commands">Config Commands<a href="#config-commands" class="hash-link" aria-label="Direct link to Config Commands" title="Direct link to Config Commands" translate="no"></a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Show current configuration</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge config show</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → Displays registry URL, token status, client ID, auto-fetch setting</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Connect to your CmdForge account (recommended)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge config connect &lt;username&gt;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → Initiates app pairing flow</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → Polls /api/v1/pairing/check/&lt;username&gt;?hostname=&lt;hostname&gt;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → On approval, saves token to ~/.cmdforge/config.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Set registry token manually (alternative to connect)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge config set-token &lt;token&gt;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → Saves token to ~/.cmdforge/config.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Set configuration values</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge config set &lt;key&gt; &lt;value&gt;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> → Available keys: auto_fetch, default_provider, registry_url</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="flags-available-on-most-commands">Flags available on most commands<a href="#flags-available-on-most-commands" class="hash-link" aria-label="Direct link to Flags available on most commands" title="Direct link to Flags available on most commands" translate="no"></a></h3>
<table><thead><tr><th>Flag</th><th>Description</th></tr></thead><tbody><tr><td><code>--offline</code></td><td>Use cached index only, don&#x27;t fetch</td></tr><tr><td><code>--refresh</code></td><td>Force refresh of cached data</td></tr><tr><td><code>--json</code></td><td>Output in JSON format</td></tr><tr><td><code>--verbose</code></td><td>Show detailed output</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="publish-state-tracking">Publish State Tracking<a href="#publish-state-tracking" class="hash-link" aria-label="Direct link to Publish State Tracking" title="Direct link to Publish State Tracking" translate="no"></a></h2>
<p>The GUI tracks the publish state of local tools to show whether they&#x27;ve been published, are pending review, or have been modified since publishing.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="local-state-storage">Local State Storage<a href="#local-state-storage" class="hash-link" aria-label="Direct link to Local State Storage" title="Direct link to Local State Storage" translate="no"></a></h3>
<p>When a tool is published, two fields are saved to the local <code>config.yaml</code>:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> my</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">tool</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> My awesome tool</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># ... tool config ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">registry_hash</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> sha256</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">abc123</span><span class="token punctuation" style="color:rgb(248, 248, 242)">...</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Hash of published config</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">registry_status</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> pending </span><span class="token comment" style="color:rgb(98, 114, 164)"># Moderation status: pending, approved, rejected</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="visual-indicators">Visual Indicators<a href="#visual-indicators" class="hash-link" aria-label="Direct link to Visual Indicators" title="Direct link to Visual Indicators" translate="no"></a></h3>
<p>The Tools page shows different indicators based on publish state:</p>
<table><thead><tr><th>State</th><th>Indicator</th><th>Meaning</th></tr></thead><tbody><tr><td>Published</td><td>✓ (green)</td><td>Approved in registry, local matches published</td></tr><tr><td>Pending</td><td>◐ (yellow)</td><td>Submitted, awaiting moderator review</td></tr><tr><td>Modified</td><td>● (orange)</td><td>Published but local config has changes</td></tr><tr><td>Local</td><td>(none)</td><td>Never published to registry</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="automatic-status-sync">Automatic Status Sync<a href="#automatic-status-sync" class="hash-link" aria-label="Direct link to Automatic Status Sync" title="Direct link to Automatic Status Sync" translate="no"></a></h3>
<p>When the Tools page loads, a background sync automatically checks the registry for status updates on all published tools. This means:</p>
<ul>
<li class="">When a moderator approves your tool, the indicator updates automatically on next visit</li>
<li class="">No manual refresh needed - just navigate to the Tools page</li>
<li class="">Status messages appear when tool statuses change</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="manual-sync">Manual Sync<a href="#manual-sync" class="hash-link" aria-label="Direct link to Manual Sync" title="Direct link to Manual Sync" translate="no"></a></h3>
<p>A &quot;Sync Status&quot; button is available to force an immediate status check for the selected tool. This is useful if you want to check status without leaving the page.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="hash-computation">Hash Computation<a href="#hash-computation" class="hash-link" aria-label="Direct link to Hash Computation" title="Direct link to Hash Computation" translate="no"></a></h3>
<p>The publish state hash is computed from the tool&#x27;s core content, excluding:</p>
<ul>
<li class=""><code>registry_hash</code> - The stored hash itself</li>
<li class=""><code>registry_status</code> - The moderation status</li>
<li class=""><code>version</code> - Publication version (added during publish)</li>
<li class=""><code>tags</code> - Publication tags (added during publish)</li>
</ul>
<p>This ensures that only actual tool content changes (steps, arguments, prompts) trigger the &quot;modified&quot; indicator.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="api-endpoint">API Endpoint<a href="#api-endpoint" class="hash-link" aria-label="Direct link to API Endpoint" title="Direct link to API Endpoint" translate="no"></a></h3>
<p>The status sync uses:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">GET /api/v1/me/tools/&lt;name&gt;/status</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Response:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">{</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;data&quot;: {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;name&quot;: &quot;my-tool&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;version&quot;: &quot;1.0.0&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;status&quot;: &quot;approved&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;config_hash&quot;: &quot;sha256:abc123...&quot;,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> &quot;published_at&quot;: &quot;2025-01-15T10:30:00Z&quot;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="webhooks-and-security">Webhooks and Security<a href="#webhooks-and-security" class="hash-link" aria-label="Direct link to Webhooks and Security" title="Direct link to Webhooks and Security" translate="no"></a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="hmac-verification">HMAC Verification<a href="#hmac-verification" class="hash-link" aria-label="Direct link to HMAC Verification" title="Direct link to HMAC Verification" translate="no"></a></h3>
<p>All Gitea webhooks are verified using HMAC-SHA256:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> hmac</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> hashlib</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">verify_webhook</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> secret</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> signature </span><span class="token operator">=</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">headers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;X-Gitea-Signature&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> signature</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token boolean">False</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> expected </span><span class="token operator">=</span><span class="token plain"> hmac</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">new</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> secret</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">encode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">body</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> hashlib</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sha256</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hexdigest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> hmac</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">compare_digest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">signature</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> expected</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="replay-protection">Replay Protection<a href="#replay-protection" class="hash-link" aria-label="Direct link to Replay Protection" title="Direct link to Replay Protection" translate="no"></a></h3>
<p>While sync is idempotent, implement basic replay protection:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">process_webhook</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> delivery_id </span><span class="token operator">=</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">headers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;X-Gitea-Delivery&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Check if already processed</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">webhook_log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">exists</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">delivery_id</span><span class="token operator">=</span><span class="token plain">delivery_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;status&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;already_processed&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">200</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Verify signature</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> verify_webhook</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> WEBHOOK_SECRET</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;error&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;invalid_signature&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">401</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Process with lock to prevent concurrent processing</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">with</span><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">lock</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f&quot;webhook:</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">delivery_id</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Double-check after acquiring lock</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">webhook_log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">exists</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">delivery_id</span><span class="token operator">=</span><span class="token plain">delivery_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;status&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;already_processed&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">200</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Process the webhook</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> result </span><span class="token operator">=</span><span class="token plain"> sync_from_repo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Log successful processing</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">webhook_log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">insert</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> delivery_id</span><span class="token operator">=</span><span class="token plain">delivery_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> event_type</span><span class="token operator">=</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;action&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> processed_at</span><span class="token operator">=</span><span class="token plain">datetime</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">utcnow</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;status&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;processed&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">200</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="sync-job-locking">Sync Job Locking<a href="#sync-job-locking" class="hash-link" aria-label="Direct link to Sync Job Locking" title="Direct link to Sync Job Locking" translate="no"></a></h3>
<p>Prevent concurrent sync operations:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Using file lock or database advisory lock</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">SYNC_LOCK_TIMEOUT </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">300</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># 5 minutes max</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">sync_from_repo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">try</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">with</span><span class="token plain"> acquire_lock</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;registry_sync&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> timeout</span><span class="token operator">=</span><span class="token plain">SYNC_LOCK_TIMEOUT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Pull latest from Gitea</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> repo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">fetch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> repo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">reset</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;origin/main&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> hard</span><span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Parse and update database</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> tool_path </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> glob</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;tools/*/*/config.yaml&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> update_tool_in_db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tool_path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Rebuild FTS index if needed</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> rebuild_fts_index</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">except</span><span class="token plain"> LockTimeout</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> logger</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">warning</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Sync already in progress, skipping&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;status&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;skipped&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;reason&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;sync_in_progress&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="atomic-sync-strategy">Atomic Sync Strategy<a href="#atomic-sync-strategy" class="hash-link" aria-label="Direct link to Atomic Sync Strategy" title="Direct link to Atomic Sync Strategy" translate="no"></a></h3>
<p>To avoid partially updated DB during webhook sync, use transactional table swap:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">sync_from_repo_atomic</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">with</span><span class="token plain"> acquire_lock</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;registry_sync&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> timeout</span><span class="token operator">=</span><span class="token plain">SYNC_LOCK_TIMEOUT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># 1. Pull latest from Gitea</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> repo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">fetch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> repo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">reset</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;origin/main&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> hard</span><span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># 2. Parse all tools into memory</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> new_tools </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> tool_path </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> glob</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;tools/*/*/config.yaml&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> tool_data </span><span class="token operator">=</span><span class="token plain"> parse_tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tool_path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> tool_data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> new_tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tool_data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># 3. Atomic swap using transaction</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">with</span><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">transaction</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Create temp table</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;CREATE TABLE tools_new AS SELECT * FROM tools WHERE 0&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Bulk insert into temp table</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> tool </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> new_tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;INSERT INTO tools_new ...&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Swap tables atomically</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;ALTER TABLE tools RENAME TO tools_old&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;ALTER TABLE tools_new RENAME TO tools&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;DROP TABLE tools_old&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Rebuild FTS index</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;INSERT INTO tools_fts(tools_fts) VALUES(&#x27;rebuild&#x27;)&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Update sync timestamp</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;UPDATE sync_status SET last_sync = ?&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">datetime</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">utcnow</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p><strong>Why atomic:</strong> Per-row updates with FTS triggers can yield inconsistent reads under load. Readers may see partial state mid-sync. Table swap ensures all-or-nothing visibility.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="error-handling">Error Handling<a href="#error-handling" class="hash-link" aria-label="Direct link to Error Handling" title="Direct link to Error Handling" translate="no"></a></h3>
<table><thead><tr><th>Error Scenario</th><th>Behavior</th></tr></thead><tbody><tr><td>Repo fetch fails</td><td>Log error, retry in 5 min, alert if 3 failures</td></tr><tr><td>YAML parse error</td><td>Skip tool, log error, continue with others</td></tr><tr><td>Database write fails</td><td>Rollback transaction, retry once, then alert</td></tr><tr><td>Lock timeout</td><td>Skip this sync, next webhook will retry</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="automated-ci-validation">Automated CI Validation<a href="#automated-ci-validation" class="hash-link" aria-label="Direct link to Automated CI Validation" title="Direct link to Automated CI Validation" translate="no"></a></h2>
<p>PRs are validated automatically using CmdForge (dogfooding):</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">PR Submitted</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">┌─────────────────────────────────────┐</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ Gitea CI runs validation tools: │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ • schema-validator │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ • security-scanner │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ • duplicate-detector │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">└───────────────┬─────────────────────┘</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> ┌───────┴───────┐</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> │ │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> All pass Any fail</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> │ │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> ▼ ▼</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Auto-merge or Add comment,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> flag for review request changes</span><br></span></code></pre></div></div>
<p>Validation checks:</p>
<ol>
<li class=""><strong>Schema validation</strong>: config.yaml matches expected format</li>
<li class=""><strong>Security scan</strong>: No dangerous shell commands, no secrets in prompts</li>
<li class=""><strong>Duplicate detection</strong>: AI-powered similarity check against existing tools</li>
<li class=""><strong>README check</strong>: README.md exists and is non-empty</li>
</ol>
<p>CI workflow (<code>.gitea/workflows/validate.yaml</code>):</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Validate Tool Submission</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">on</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">pull_request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">jobs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">validate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">runs-on</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ubuntu</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">latest</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">steps</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">uses</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> actions/checkout@v3</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Validate schema</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> python scripts/validate_tool.py $</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> github.event.pull_request.head.sha </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Security scan</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> cmdforge run security</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">scanner &lt; changed_files.txt</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Check duplicates</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> cmdforge run duplicate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">detector &lt; changed_files.txt</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="registry-repository-structure">Registry Repository Structure<a href="#registry-repository-structure" class="hash-link" aria-label="Direct link to Registry Repository Structure" title="Direct link to Registry Repository Structure" translate="no"></a></h2>
<p>Full structure of the CmdForge-Registry repo:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">CmdForge-Registry/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── README.md # Registry overview</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── CONTRIBUTING.md # How to submit tools</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── LICENSE</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── tools/ # All published tools</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ ├── rob/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ │ ├── summarize/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ │ │ ├── config.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ │ │ └── README.md</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ │ └── translate/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ │ ├── config.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ │ └── README.md</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ └── alice/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ └── code-review/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ ├── config.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ └── README.md</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── categories/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ └── categories.yaml # Category definitions</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── collections/ # Curated tool collections</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ ├── text-processing-essentials.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ ├── developer-toolkit.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ └── data-pipeline-basics.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── index.json # Auto-generated search index</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── .gitea/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ └── workflows/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ ├── validate.yaml # PR validation</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ ├── build-index.yaml # Rebuild index on merge</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ └── notify-api.yaml # Webhook to API server</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">└── scripts/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> ├── validate_tool.py # Schema validation</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> ├── build_index.py # Generate index.json</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> ├── check_duplicates.py # Similarity detection</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> └── security_scan.py # Security checks</span><br></span></code></pre></div></div>
<p><code>categories.yaml</code> format:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">categories</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">processing</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Tools for manipulating and analyzing text</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">icon</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 📝</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> code</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Tools for code review</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> generation</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> and analysis</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">icon</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 💻</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> data</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Tools for data transformation and analysis</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">icon</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 📊</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> media</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Tools for image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> audio</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> and video processing</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">icon</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 🎨</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> productivity</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> General productivity and automation tools</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token key atrule">icon</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="download-stats">Download Stats<a href="#download-stats" class="hash-link" aria-label="Direct link to Download Stats" title="Direct link to Download Stats" translate="no"></a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="counting-methodology">Counting Methodology<a href="#counting-methodology" class="hash-link" aria-label="Direct link to Counting Methodology" title="Direct link to Counting Methodology" translate="no"></a></h3>
<ul>
<li class="">Count installs only, not views or searches</li>
<li class="">Increment <strong>after</strong> successful download (response sent)</li>
<li class="">Dedupe by <code>client_id + tool_id + date</code></li>
</ul>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">download_tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">owner</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> version</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> install</span><span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> client_id</span><span class="token operator">=</span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> tool </span><span class="token operator">=</span><span class="token plain"> get_tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">owner</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> version</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;error&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;not_found&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">404</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> config_yaml </span><span class="token operator">=</span><span class="token plain"> tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">config_yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Only count if this is an install (not just viewing)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> install</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> record_download</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token builtin" style="color:rgb(189, 147, 249)">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> client_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;config&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> config_yaml</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">200</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">record_download</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tool_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> client_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> today </span><span class="token operator">=</span><span class="token plain"> date</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">today</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Use client_id if provided, otherwise generate anonymous fallback</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> effective_client_id </span><span class="token operator">=</span><span class="token plain"> client_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">or</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f&quot;anon_</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation builtin" style="color:rgb(189, 147, 249)">hash</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string-interpolation interpolation">request</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token string-interpolation interpolation">remote_addr</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Dedupe: only count once per client per tool per day</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">try</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">download_stats</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">insert</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> tool_id</span><span class="token operator">=</span><span class="token plain">tool_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> client_id</span><span class="token operator">=</span><span class="token plain">effective_client_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> downloaded_at</span><span class="token operator">=</span><span class="token plain">today</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Increment counter (can be async/batch updated)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;UPDATE tools SET downloads = downloads + 1 WHERE id = ?&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">tool_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">except</span><span class="token plain"> IntegrityError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">pass</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Already counted today, ignore</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="client-id-generation">Client ID Generation<a href="#client-id-generation" class="hash-link" aria-label="Direct link to Client ID Generation" title="Direct link to Client ID Generation" translate="no"></a></h3>
<p>CLI generates a persistent anonymous ID on first run:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># In CLI, on first run</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> uuid</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> os</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">CONFIG_PATH </span><span class="token operator">=</span><span class="token plain"> os</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">expanduser</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;~/.cmdforge/config.yaml&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">get_or_create_client_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> config </span><span class="token operator">=</span><span class="token plain"> load_config</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;client_id&#x27;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> config</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> config</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;client_id&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f&quot;anon_</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">uuid</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token string-interpolation interpolation">uuid4</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token string-interpolation interpolation builtin" style="color:rgb(189, 147, 249)">hex</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token string-interpolation interpolation format-spec">16]</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> save_config</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">config</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> config</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;client_id&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></span></code></pre></div></div>
<p><strong>Fallback when client_id missing:</strong></p>
<ul>
<li class="">If header <code>X-Client-ID</code> not sent, use IP hash as fallback</li>
<li class="">This still provides some dedupe for anonymous users</li>
<li class="">Logged users&#x27; downloads are attributed to their account instead</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="privacy-considerations">Privacy Considerations<a href="#privacy-considerations" class="hash-link" aria-label="Direct link to Privacy Considerations" title="Direct link to Privacy Considerations" translate="no"></a></h3>
<ul>
<li class="">No IP addresses stored in database</li>
<li class=""><code>client_id</code> is client-controlled and can be regenerated</li>
<li class="">Stats are aggregated (total count), not individual tracking</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="async-stats-strategy">Async Stats Strategy<a href="#async-stats-strategy" class="hash-link" aria-label="Direct link to Async Stats Strategy" title="Direct link to Async Stats Strategy" translate="no"></a></h3>
<p>To avoid DB contention on the hot download path:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> queue </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> Queue</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> threading </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> Thread</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># In-memory queue for stats</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">stats_queue </span><span class="token operator">=</span><span class="token plain"> Queue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">record_download_async</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tool_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> client_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">&quot;&quot;&quot;Non-blocking: enqueue for background processing&quot;&quot;&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> stats_queue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">put</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;tool_id&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> tool_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;client_id&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> client_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;date&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> date</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">today</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">stats_worker</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">&quot;&quot;&quot;Background thread: batch process stats every 5 seconds&quot;&quot;&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> batch </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">while</span><span class="token plain"> </span><span class="token boolean">True</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">try</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> item </span><span class="token operator">=</span><span class="token plain"> stats_queue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">timeout</span><span class="token operator">=</span><span class="token number">5</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> batch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">except</span><span class="token plain"> Empty</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> batch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> flush_batch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">batch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> batch </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">flush_batch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">batch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">&quot;&quot;&quot;Bulk insert with conflict ignore&quot;&quot;&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">with</span><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">transaction</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> item </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> batch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">try</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">execute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">&quot;&quot;&quot;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)"> INSERT INTO download_stats (tool_id, client_id, downloaded_at)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)"> VALUES (?, ?, ?)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)"> ON CONFLICT DO NOTHING</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)"> &quot;&quot;&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;tool_id&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;client_id&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;date&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">except</span><span class="token plain"> Exception </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> e</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> logger</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">warning</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f&quot;Stats insert failed: </span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">e</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Don&#x27;t fail downloads for stats errors</span><br></span></code></pre></div></div>
<p><strong>Failure behavior:</strong> If stats DB write fails, log the error but don&#x27;t fail the download. Stats are &quot;best effort&quot; - the download must succeed.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="search">Search<a href="#search" class="hash-link" aria-label="Direct link to Search" title="Direct link to Search" translate="no"></a></h2>
<ul>
<li class="">Primary search: SQLite FTS5 inside the API.</li>
<li class=""><code>index.json</code> provides offline CLI search and backup.</li>
<li class="">If FTS5 is stale, return results with <code>X-Search-Index-Stale: true</code>.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="api-caching-strategy">API Caching Strategy<a href="#api-caching-strategy" class="hash-link" aria-label="Direct link to API Caching Strategy" title="Direct link to API Caching Strategy" translate="no"></a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cache-headers">Cache Headers<a href="#cache-headers" class="hash-link" aria-label="Direct link to Cache Headers" title="Direct link to Cache Headers" translate="no"></a></h3>
<table><thead><tr><th>Endpoint</th><th>Cache-Control</th><th>ETag</th><th>Notes</th></tr></thead><tbody><tr><td><code>GET /index.json</code></td><td><code>max-age=300, stale-while-revalidate=60</code></td><td>Yes</td><td>5 min cache, background refresh</td></tr><tr><td><code>GET /tools/{owner}/{name}</code></td><td><code>max-age=60</code></td><td>Yes</td><td>1 min cache</td></tr><tr><td><code>GET /tools/{owner}/{name}/download</code></td><td><code>max-age=3600, immutable</code></td><td>Yes</td><td>Immutable versions, 1 hour</td></tr><tr><td><code>GET /tools/search</code></td><td><code>no-cache</code></td><td>No</td><td>Always fresh</td></tr><tr><td><code>GET /categories</code></td><td><code>max-age=3600</code></td><td>Yes</td><td>Categories change rarely</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="etag-implementation">ETag Implementation<a href="#etag-implementation" class="hash-link" aria-label="Direct link to ETag Implementation" title="Direct link to ETag Implementation" translate="no"></a></h3>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> hashlib</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> datetime </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> datetime</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">get_tool_etag</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">&quot;&quot;&quot;Generate ETag from tool identity (immutable versions don&#x27;t change)&quot;&quot;&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Since versions are immutable, owner/name@version is stable</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Use published_at for extra safety (not updated_at, which doesn&#x27;t exist)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> content </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f&quot;</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">tool</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token string-interpolation interpolation">owner</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">/</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">tool</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token string-interpolation interpolation">name</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">@</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">tool</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token string-interpolation interpolation">version</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">:</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">tool</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token string-interpolation interpolation">published_at</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token string-interpolation interpolation">isoformat</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> hashlib</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">md5</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">content</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">encode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hexdigest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">get_index_etag</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">&quot;&quot;&quot;Generate ETag from last sync timestamp&quot;&quot;&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> last_sync </span><span class="token operator">=</span><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get_last_sync_time</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> hashlib</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">md5</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">last_sync</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">isoformat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">encode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hexdigest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">@app</span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">route</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;/api/v1/tools/&lt;owner&gt;/&lt;name&gt;/download&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">download_tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">owner</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> version </span><span class="token operator">=</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">args</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;version&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;latest&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> tool </span><span class="token operator">=</span><span class="token plain"> resolve_and_get_tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">owner</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> version</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> etag </span><span class="token operator">=</span><span class="token plain"> get_tool_etag</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Check If-None-Match header</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">headers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;If-None-Match&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">==</span><span class="token plain"> etag</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">304</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Not Modified</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> response </span><span class="token operator">=</span><span class="token plain"> jsonify</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;data&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;owner&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">owner</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;name&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;resolved_version&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">version</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;config&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> tool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">config_yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">headers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;ETag&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> etag</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">headers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;Cache-Control&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;max-age=3600, immutable&#x27;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> response</span><br></span></code></pre></div></div>
<p><strong>Note:</strong> Since tool versions are immutable, the ETag based on <code>owner/name@version</code> is permanently stable. The <code>published_at</code> timestamp is included for defense-in-depth but won&#x27;t change.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="db-vs-repo-read-strategy">DB vs Repo Read Strategy<a href="#db-vs-repo-read-strategy" class="hash-link" aria-label="Direct link to DB vs Repo Read Strategy" title="Direct link to DB vs Repo Read Strategy" translate="no"></a></h3>
<table><thead><tr><th>Scenario</th><th>Read From</th><th>Reason</th></tr></thead><tbody><tr><td>Normal operation</td><td>SQLite DB</td><td>Fast, indexed, FTS</td></tr><tr><td>DB empty/corrupted</td><td>Gitea repo</td><td>Fallback/recovery</td></tr><tr><td>Webhook sync in progress</td><td>DB (stale OK)</td><td>Avoid blocking reads</td></tr><tr><td>Search query</td><td>SQLite FTS5</td><td>Full-text search</td></tr><tr><td>Download specific version</td><td>DB, fallback to repo</td><td>DB is cache, repo is truth</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="staleness-detection">Staleness Detection<a href="#staleness-detection" class="hash-link" aria-label="Direct link to Staleness Detection" title="Direct link to Staleness Detection" translate="no"></a></h3>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">STALE_THRESHOLD </span><span class="token operator">=</span><span class="token plain"> timedelta</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">minutes</span><span class="token operator">=</span><span class="token number">10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">is_db_stale</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> last_sync </span><span class="token operator">=</span><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get_last_sync_time</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> datetime</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">utcnow</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token plain"> last_sync </span><span class="token operator">&gt;</span><span class="token plain"> STALE_THRESHOLD</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">@app</span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">route</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;/tools/search&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">search_tools</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">q</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> results </span><span class="token operator">=</span><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">search_fts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">q</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> response </span><span class="token operator">=</span><span class="token plain"> jsonify</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;results&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> results</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> is_db_stale</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">headers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;X-Search-Index-Stale&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;true&#x27;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">headers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;X-Last-Sync&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get_last_sync_time</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">isoformat</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> response</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="error-model">Error Model<a href="#error-model" class="hash-link" aria-label="Direct link to Error Model" title="Direct link to Error Model" translate="no"></a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="response-envelopes">Response Envelopes<a href="#response-envelopes" class="hash-link" aria-label="Direct link to Response Envelopes" title="Direct link to Response Envelopes" translate="no"></a></h3>
<p><strong>Success response:</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;data&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> ... </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;meta&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;page&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;per_page&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">20</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;total&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">42</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;total_pages&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">3</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p><strong>Error response:</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;error&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;code&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;TOOL_NOT_FOUND&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;message&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Tool &#x27;foo/bar&#x27; does not exist&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;details&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;owner&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;foo&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;name&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;bar&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;suggestion&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Did you mean &#x27;rob/bar&#x27;?&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;docs_url&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;https://cmdforge.brrd.tech/docs/errors#TOOL_NOT_FOUND&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="error-codes">Error Codes<a href="#error-codes" class="hash-link" aria-label="Direct link to Error Codes" title="Direct link to Error Codes" translate="no"></a></h3>
<table><thead><tr><th>Code</th><th>HTTP</th><th>Description</th></tr></thead><tbody><tr><td><code>TOOL_NOT_FOUND</code></td><td>404</td><td>Tool does not exist</td></tr><tr><td><code>VERSION_NOT_FOUND</code></td><td>404</td><td>Requested version doesn&#x27;t exist</td></tr><tr><td><code>VERSION_EXISTS</code></td><td>409</td><td>Cannot overwrite published version</td></tr><tr><td><code>INVALID_VERSION</code></td><td>400</td><td>Version string is not valid semver</td></tr><tr><td><code>INVALID_CONSTRAINT</code></td><td>400</td><td>Version constraint syntax error</td></tr><tr><td><code>CONSTRAINT_UNSATISFIABLE</code></td><td>404</td><td>No version matches constraint</td></tr><tr><td><code>VALIDATION_ERROR</code></td><td>400</td><td>Tool config validation failed</td></tr><tr><td><code>UNAUTHORIZED</code></td><td>401</td><td>Missing or invalid auth token</td></tr><tr><td><code>FORBIDDEN</code></td><td>403</td><td>Token valid but lacks permission</td></tr><tr><td><code>RATE_LIMITED</code></td><td>429</td><td>Too many requests</td></tr><tr><td><code>SLUG_TAKEN</code></td><td>409</td><td>Namespace slug already registered</td></tr><tr><td><code>ACCOUNT_LOCKED</code></td><td>403</td><td>Too many failed login attempts</td></tr><tr><td><code>SERVER_ERROR</code></td><td>500</td><td>Internal error (logged for debugging)</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="error-scenarios-and-fallbacks">Error Scenarios and Fallbacks<a href="#error-scenarios-and-fallbacks" class="hash-link" aria-label="Direct link to Error Scenarios and Fallbacks" title="Direct link to Error Scenarios and Fallbacks" translate="no"></a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cli-error-handling">CLI Error Handling<a href="#cli-error-handling" class="hash-link" aria-label="Direct link to CLI Error Handling" title="Direct link to CLI Error Handling" translate="no"></a></h3>
<table><thead><tr><th>Scenario</th><th>CLI Behavior</th><th>User Message</th></tr></thead><tbody><tr><td>Registry offline</td><td>Use cached tools if available</td><td>&quot;Registry unavailable. Using cached version.&quot;</td></tr><tr><td>Tool not found</td><td>Check cache, then fail</td><td>&quot;Tool &#x27;foo/bar&#x27; not found in registry or cache.&quot;</td></tr><tr><td>Version constraint unsatisfiable</td><td>Show available versions</td><td>&quot;No version matches &#x27;&gt;=5.0.0&#x27;. Available: 1.0.0, 1.1.0, 1.2.0&quot;</td></tr><tr><td>Auth token expired</td><td>Prompt for new token</td><td>&quot;Token expired. Please re-authenticate.&quot;</td></tr><tr><td>Rate limited</td><td>Wait and retry (backoff)</td><td>&quot;Rate limited. Retrying in 30 seconds...&quot;</td></tr><tr><td>Network timeout</td><td>Retry with backoff, then fail</td><td>&quot;Connection timed out. Check your network.&quot;</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="validation-failure-details">Validation Failure Details<a href="#validation-failure-details" class="hash-link" aria-label="Direct link to Validation Failure Details" title="Direct link to Validation Failure Details" translate="no"></a></h3>
<p>When <code>VALIDATION_ERROR</code> occurs, provide specific field errors:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;error&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;code&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;VALIDATION_ERROR&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;message&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Tool configuration is invalid&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;details&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;errors&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;path&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;steps[0].provider&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;message&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Provider &#x27;gpt5&#x27; is not recognized&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;allowed&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;claude&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;openai&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;ollama&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;mock&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;path&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;version&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;message&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Version &#x27;1.0&#x27; is not valid semver (use &#x27;1.0.0&#x27;)&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;docs_url&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;https://cmdforge.brrd.tech/docs/tool-format&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="dependency-resolution-failures">Dependency Resolution Failures<a href="#dependency-resolution-failures" class="hash-link" aria-label="Direct link to Dependency Resolution Failures" title="Direct link to Dependency Resolution Failures" translate="no"></a></h3>
<p>When <code>cmdforge install</code> fails on a manifest:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge install</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Error: Could not resolve all dependencies</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> rob/summarize@^2.0.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> ✗ No matching version (latest: 1.2.0)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> alice/translate@&gt;=1.0.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> ✓ Found 1.3.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Suggestions:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> - Update rob/summarize constraint to &quot;^1.0.0&quot;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> - Contact the tool author for a v2 release</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="graceful-degradation">Graceful Degradation<a href="#graceful-degradation" class="hash-link" aria-label="Direct link to Graceful Degradation" title="Direct link to Graceful Degradation" translate="no"></a></h3>
<table><thead><tr><th>Component Down</th><th>Fallback Behavior</th></tr></thead><tbody><tr><td>API server</td><td>CLI uses <code>~/.cmdforge/registry/index.json</code> for search</td></tr><tr><td>Gitea repo</td><td>API serves from DB cache (may be stale)</td></tr><tr><td>FTS5 index</td><td>Fall back to LIKE queries (slower but works)</td></tr><tr><td>Network</td><td>Use locally installed tools, skip registry features</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ux-requirements-clitui">UX Requirements (CLI/TUI)<a href="#ux-requirements-clitui" class="hash-link" aria-label="Direct link to UX Requirements (CLI/TUI)" title="Direct link to UX Requirements (CLI/TUI)" translate="no"></a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="publishing-ux">Publishing UX<a href="#publishing-ux" class="hash-link" aria-label="Direct link to Publishing UX" title="Direct link to Publishing UX" translate="no"></a></h3>
<ul>
<li class="">
<p><code>cmdforge registry publish --dry-run</code> validates locally and shows what would be submitted:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge registry publish --dry-run</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Validating tool...</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">✓ config.yaml is valid</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">✓ README.md exists (2.3 KB)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">✓ Version 1.1.0 not yet published</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Would submit:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Owner: rob</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Name: summarize</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Version: 1.1.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Category: text-processing</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Tags: summarization, ai, text</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Config preview:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">─────────────────────────────</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">name: summarize</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">version: &quot;1.1.0&quot;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">description: Summarize text using AI</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">...</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">─────────────────────────────</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Run without --dry-run to submit for review.</span><br></span></code></pre></div></div>
</li>
<li class="">
<p><strong>Version bump reminder:</strong> CLI warns if version hasn&#x27;t changed from published:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">⚠ Version 1.0.0 is already published. Bump version in config.yaml to publish changes.</span><br></span></code></pre></div></div>
</li>
<li class="">
<p>First-time publishing flow prompts for token and saves it to config.</p>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="progress-indicators">Progress Indicators<a href="#progress-indicators" class="hash-link" aria-label="Direct link to Progress Indicators" title="Direct link to Progress Indicators" translate="no"></a></h3>
<p>Long-running operations show progress:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge install</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Installing project dependencies...</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> [1/3] rob/summarize@^1.0.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Resolving version... 1.2.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Downloading... done</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Installing... done ✓</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> [2/3] alice/translate@&gt;=2.0.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Resolving version... 2.1.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Downloading... done</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Installing... done ✓</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> [3/3] official/code-review@*</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Resolving version... 1.0.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Downloading... done</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Installing... done ✓</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">✓ Installed 3 tools</span><br></span></code></pre></div></div>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge registry publish</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Submitting rob/summarize@1.1.0...</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Validating... done ✓</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Uploading... done ✓</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> Creating PR... done ✓</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">✓ PR created: https://gitea.brrd.tech/rob/CmdForge-Registry/pulls/42</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Your tool is pending review. You&#x27;ll receive an email when it&#x27;s approved.</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="tui-browse">TUI Browse<a href="#tui-browse" class="hash-link" aria-label="Direct link to TUI Browse" title="Direct link to TUI Browse" translate="no"></a></h3>
<p><code>cmdforge registry browse</code> opens a full-screen terminal UI:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">┌─ CmdForge Registry ───────────────────────────────────────┐</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ Search: [________________] [All Categories ▼] [Sort: Popular ▼] │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├─────────────────────────────────────────────────────────────┤</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ ▶ rob/summarize v1.2.0 ⬇ 142 │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ Summarize text using AI │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ [text-processing] [ai] [summarization] │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ alice/translate v2.1.0 ⬇ 98 │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ Translate text between languages │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ [text-processing] [translation] │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ official/code-review v1.0.0 ⬇ 87 │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ AI-powered code review │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ [code] [review] [ai] │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├─────────────────────────────────────────────────────────────┤</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ ↑↓ Navigate Enter: Details i: Install /: Search q: Quit │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">└─────────────────────────────────────────────────────────────┘</span><br></span></code></pre></div></div>
<p><strong>Keyboard shortcuts:</strong></p>
<table><thead><tr><th>Key</th><th>Action</th></tr></thead><tbody><tr><td><code>↑/↓</code> or <code>j/k</code></td><td>Navigate list</td></tr><tr><td><code>Enter</code></td><td>View tool details</td></tr><tr><td><code>i</code></td><td>Install selected tool</td></tr><tr><td><code>/</code></td><td>Focus search box</td></tr><tr><td><code>c</code></td><td>Change category filter</td></tr><tr><td><code>s</code></td><td>Change sort order</td></tr><tr><td><code>?</code></td><td>Show help</td></tr><tr><td><code>q</code></td><td>Quit</td></tr></tbody></table>
<p><strong>Virtual scrolling:</strong> For large tool lists (&gt;100), use virtual scrolling to maintain performance.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="project-initialization">Project Initialization<a href="#project-initialization" class="hash-link" aria-label="Direct link to Project Initialization" title="Direct link to Project Initialization" translate="no"></a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ cmdforge init</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Creating cmdforge.yaml...</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Project name [my-project]: my-ai-project</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Version [1.0.0]:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Would you like to add any tools? (search with &#x27;s&#x27;, skip with Enter)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">&gt; s</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Search: summ</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> 1. rob/summarize v1.2.0 - Summarize text using AI</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> 2. alice/summary v1.0.0 - Generate summaries</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Add tool (number, or Enter to finish): 1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Added rob/summarize@^1.2.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Add tool (number, or Enter to finish):</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">✓ Created cmdforge.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">name: my-ai-project</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">version: &quot;1.0.0&quot;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">dependencies:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> - name: rob/summarize</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> version: &quot;^1.2.0&quot;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Run &#x27;cmdforge install&#x27; to install dependencies.</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="accessibility">Accessibility<a href="#accessibility" class="hash-link" aria-label="Direct link to Accessibility" title="Direct link to Accessibility" translate="no"></a></h3>
<ul>
<li class=""><strong>CLI:</strong> All output works with screen readers, no color-only information</li>
<li class=""><strong>TUI:</strong> Full keyboard navigation, high-contrast mode support</li>
<li class=""><strong>Web UI:</strong> WCAG 2.1 AA compliance target<!-- -->
<ul>
<li class="">Semantic HTML</li>
<li class="">ARIA labels for interactive elements</li>
<li class="">Focus management in modals</li>
<li class="">Skip links for navigation</li>
</ul>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="offline-cache">Offline Cache<a href="#offline-cache" class="hash-link" aria-label="Direct link to Offline Cache" title="Direct link to Offline Cache" translate="no"></a></h2>
<p>Cache registry index locally:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">~/.cmdforge/registry/index.json</span><br></span></code></pre></div></div>
<p>Refresh when older than 24 hours; support <code>--offline</code> and <code>--refresh</code> flags.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="index-integrity">Index Integrity<a href="#index-integrity" class="hash-link" aria-label="Direct link to Index Integrity" title="Direct link to Index Integrity" translate="no"></a></h3>
<p>The cached <code>index.json</code> includes integrity metadata:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;version&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;1.0&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;generated_at&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;2025-01-20T12:00:00Z&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;checksum&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;sha256:abc123...&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;tool_count&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">142</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;tools&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">...</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p><strong>API response headers:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">ETag: &quot;abc123def456&quot;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">X-Index-Checksum: sha256:abc123...</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">X-Index-Generated: 2025-01-20T12:00:00Z</span><br></span></code></pre></div></div>
<p><strong>CLI verification:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">verify_cached_index</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">&quot;&quot;&quot;Verify cached index integrity on load&quot;&quot;&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> cached </span><span class="token operator">=</span><span class="token plain"> load_cached_index</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> cached</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Verify checksum</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> content </span><span class="token operator">=</span><span class="token plain"> json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">dumps</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">cached</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;tools&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> sort_keys</span><span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> computed </span><span class="token operator">=</span><span class="token plain"> hashlib</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">sha256</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">content</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">encode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">hexdigest</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> computed </span><span class="token operator">!=</span><span class="token plain"> cached</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;checksum&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">replace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;sha256:&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> logger</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">warning</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Cached index checksum mismatch, will refresh&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> cached</span><br></span></code></pre></div></div>
<p><strong>Corruption handling:</strong></p>
<ul>
<li class="">If checksum fails, discard cache and fetch fresh</li>
<li class="">If partial write detected (missing fields), discard and refresh</li>
<li class="">CLI shows warning: &quot;Cached index corrupted, fetching fresh copy...&quot;</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="web-ui-vision">Web UI Vision<a href="#web-ui-vision" class="hash-link" aria-label="Direct link to Web UI Vision" title="Direct link to Web UI Vision" translate="no"></a></h2>
<p>The registry includes a full website, not just an API:</p>
<p><strong>Site structure:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">cmdforge.brrd.tech (or gitea.brrd.tech/registry)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── / # Landing page</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── /tools # Browse all tools</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── /tools/{owner}/{name} # Tool detail page</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── /categories # Browse by category</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── /categories/{name} # Tools in category</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── /collections # Browse curated collections</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── /collections/{name} # Collection detail page</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── /search?q=... # Search results</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── /docs # Documentation</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ ├── /docs/getting-started</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ ├── /docs/creating-tools</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ ├── /docs/publishing</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ └── /docs/best-practices</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── /tutorials # Step-by-step guides</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ ├── /tutorials/first-tool</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ ├── /tutorials/chaining-steps</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ └── /tutorials/code-steps</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── /examples # Example projects</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── /blog # Updates, announcements (optional)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── /register # Publisher registration</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── /login # Publisher login</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── /dashboard # Publisher dashboard</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ ├── /dashboard/tools # My published tools</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ ├── /dashboard/connections # Connected apps</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ └── /dashboard/settings # Account settings</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">└── /api/v1/... # API endpoints</span><br></span></code></pre></div></div>
<p><strong>Landing page content:</strong></p>
<ul>
<li class="">Hero: &quot;Share and discover AI-powered CLI tools&quot;</li>
<li class="">Quick install example</li>
<li class="">Featured/popular tools</li>
<li class="">Category highlights</li>
<li class="">&quot;Get Started&quot; CTA</li>
</ul>
<p><strong>Tool detail page:</strong></p>
<ul>
<li class="">Name, description, version, author</li>
<li class="">README rendered as markdown (sanitized)</li>
<li class="">Install command (copy-to-clipboard)</li>
<li class="">Version history</li>
<li class="">Download stats</li>
<li class="">Category/tags</li>
<li class="">&quot;Report&quot; button for abuse</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="readme-security">README Security<a href="#readme-security" class="hash-link" aria-label="Direct link to README Security" title="Direct link to README Security" translate="no"></a></h3>
<p>When rendering README markdown, apply XSS sanitization:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> bleach</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> markdown </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> markdown</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">ALLOWED_TAGS </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;h1&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;h2&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;h3&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;h4&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;h5&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;h6&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;p&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;br&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;hr&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;ul&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;ol&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;li&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;strong&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;em&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;code&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;pre&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;blockquote&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;a&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;img&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;table&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;thead&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;tbody&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;tr&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;th&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;td&#x27;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">ALLOWED_ATTRS </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;a&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;href&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;title&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;img&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;src&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;alt&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;title&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;code&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;class&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># for syntax highlighting</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">render_readme_safe</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">readme_raw</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token triple-quoted-string string" style="color:rgb(255, 121, 198)">&quot;&quot;&quot;Convert markdown to sanitized HTML&quot;&quot;&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Convert markdown to HTML</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> html </span><span class="token operator">=</span><span class="token plain"> markdown</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">readme_raw</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> extensions</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;fenced_code&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;tables&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Sanitize to prevent XSS</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> safe_html </span><span class="token operator">=</span><span class="token plain"> bleach</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">clean</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> html</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> tags</span><span class="token operator">=</span><span class="token plain">ALLOWED_TAGS</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> attributes</span><span class="token operator">=</span><span class="token plain">ALLOWED_ATTRS</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> strip</span><span class="token operator">=</span><span class="token boolean">True</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># Linkify URLs</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> safe_html </span><span class="token operator">=</span><span class="token plain"> bleach</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">linkify</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">safe_html</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> safe_html</span><br></span></code></pre></div></div>
<p><strong>Storage strategy:</strong></p>
<ul>
<li class="">Store raw README in <code>tools.readme</code></li>
<li class="">Render and sanitize on request (or cache rendered HTML)</li>
<li class="">Never trust client-submitted HTML directly</li>
</ul>
<p><strong>Tech stack options:</strong></p>
<table><thead><tr><th>Option</th><th>Pros</th><th>Cons</th></tr></thead><tbody><tr><td>Flask + Jinja + Tailwind</td><td>Simple, Python-only, fast to build</td><td>Less interactive</td></tr><tr><td>FastAPI + Vue/React SPA</td><td>Modern, interactive</td><td>More complex, separate build</td></tr><tr><td>Astro/Next.js</td><td>Great SEO, static-first</td><td>Different stack (Node.js)</td></tr></tbody></table>
<p><strong>Recommendation:</strong> Flask + Jinja + Tailwind for v1</p>
<ul>
<li class="">Keeps everything in Python</li>
<li class="">Server-rendered is fine for a registry</li>
<li class="">Good SEO out of the box</li>
<li class="">Can add interactivity with Alpine.js or htmx if needed</li>
</ul>
<p><strong>Monetization considerations:</strong></p>
<ul>
<li class="">AdSense-compatible (server-rendered pages)</li>
<li class="">Analytics tracking for traffic insights</li>
<li class="">Future: sponsored tools, featured placements</li>
<li class="">Future: premium publisher tiers (more tools, priority review)</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="registry-curation-system">Registry Curation System<a href="#registry-curation-system" class="hash-link" aria-label="Direct link to Registry Curation System" title="Direct link to Registry Curation System" translate="no"></a></h2>
<p>The registry includes a moderation system for content curation, abuse prevention, and quality control.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="roles-and-permissions">Roles and Permissions<a href="#roles-and-permissions" class="hash-link" aria-label="Direct link to Roles and Permissions" title="Direct link to Roles and Permissions" translate="no"></a></h3>
<table><thead><tr><th>Role</th><th>Permissions</th></tr></thead><tbody><tr><td><code>user</code></td><td>Publish tools, manage own tools, view public content</td></tr><tr><td><code>moderator</code></td><td>All user permissions + approve/reject tools, resolve reports, view all publishers</td></tr><tr><td><code>admin</code></td><td>All moderator permissions + ban/unban publishers, change roles, delete tools, view audit log</td></tr></tbody></table>
<p><strong>Database columns:</strong></p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">-- In publishers table</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">role </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;user&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- &#x27;user&#x27;, &#x27;moderator&#x27;, &#x27;admin&#x27;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">banned </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">banned_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">banned_by </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">ban_reason </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">-- In tools table</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">visibility </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;public&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- &#x27;public&#x27;, &#x27;private&#x27;, &#x27;unlisted&#x27;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">moderation_status </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;pending&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- &#x27;pending&#x27;, &#x27;approved&#x27;, &#x27;rejected&#x27;, &#x27;removed&#x27;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">moderation_note </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">moderated_by </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">moderated_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="tool-visibility">Tool Visibility<a href="#tool-visibility" class="hash-link" aria-label="Direct link to Tool Visibility" title="Direct link to Tool Visibility" translate="no"></a></h3>
<table><thead><tr><th>Visibility</th><th>In Search/List</th><th>Direct Link</th><th>Who Can See</th></tr></thead><tbody><tr><td><code>public</code></td><td>Yes (if approved)</td><td>Yes (if approved)</td><td>Everyone</td></tr><tr><td><code>private</code></td><td>No</td><td>No</td><td>Owner only</td></tr><tr><td><code>unlisted</code></td><td>No</td><td>Yes (if approved)</td><td>Anyone with link</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="moderation-workflow">Moderation Workflow<a href="#moderation-workflow" class="hash-link" aria-label="Direct link to Moderation Workflow" title="Direct link to Moderation Workflow" translate="no"></a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">Tool Published</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> ├── visibility = &#x27;private&#x27; or &#x27;unlisted&#x27;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> │ └── Auto-approved (moderation_status = &#x27;approved&#x27;)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> └── visibility = &#x27;public&#x27;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> └── moderation_status = &#x27;pending&#x27;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> ├── Moderator approves → &#x27;approved&#x27; → Visible in search</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> ├── Moderator rejects → &#x27;rejected&#x27; → Not visible</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> └── Moderator removes → &#x27;removed&#x27; → Removed from view</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="admin-api-endpoints">Admin API Endpoints<a href="#admin-api-endpoints" class="hash-link" aria-label="Direct link to Admin API Endpoints" title="Direct link to Admin API Endpoints" translate="no"></a></h3>
<p><strong>Tool Moderation (moderator+):</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">GET /api/v1/admin/tools/pending # List pending tools</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">GET /api/v1/admin/tools/&lt;id&gt; # Get full tool details for review</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">POST /api/v1/admin/tools/&lt;id&gt;/approve # Approve a tool</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">POST /api/v1/admin/tools/&lt;id&gt;/reject # Reject with reason (required)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">POST /api/v1/admin/tools/&lt;id&gt;/remove # Soft-delete approved tool</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">DELETE /api/v1/admin/tools/&lt;id&gt; # Hard delete (admin only)</span><br></span></code></pre></div></div>
<p><strong>Tool Detail Response (<code>GET /api/v1/admin/tools/&lt;id&gt;</code>):</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;data&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;id&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">123</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;owner&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;alice&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;name&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;my-tool&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;version&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;1.0.0&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;description&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Tool description&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;category&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Text Processing&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;tags&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;ai,text&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;published_at&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;2025-01-15T10:30:00Z&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;publisher_name&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Alice Smith&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;visibility&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;public&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;moderation_status&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;pending&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;scrutiny_status&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;pending_review&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;scrutiny_report&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;findings&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token property">&quot;check&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;shell_commands&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">&quot;result&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;warning&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">&quot;message&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;...&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token property">&quot;suggestion&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;...&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;config&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;name&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;my-tool&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;description&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;...&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;arguments&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">...</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;steps&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">...</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;readme&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;# My Tool\n\nDocumentation here...&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p><strong>Publisher Management (moderator+ to view, admin to modify):</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">GET /api/v1/admin/publishers # List all publishers</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">GET /api/v1/admin/publishers/&lt;id&gt; # Publisher details</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">POST /api/v1/admin/publishers/&lt;id&gt;/ban # Ban with reason (admin)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">POST /api/v1/admin/publishers/&lt;id&gt;/unban # Unban (admin)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">POST /api/v1/admin/publishers/&lt;id&gt;/role # Change role (admin)</span><br></span></code></pre></div></div>
<p><strong>Reports (moderator+):</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">GET /api/v1/admin/reports # List reports</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">POST /api/v1/admin/reports/&lt;id&gt;/resolve # Resolve with action</span><br></span></code></pre></div></div>
<p><strong>Audit Log (admin only):</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">GET /api/v1/admin/audit-log # View moderation history</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> ?target_type=tool|publisher</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> ?target_id=&lt;id&gt;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> ?actor_id=&lt;id&gt;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> ?since=&lt;date&gt;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="ban-behavior">Ban Behavior<a href="#ban-behavior" class="hash-link" aria-label="Direct link to Ban Behavior" title="Direct link to Ban Behavior" translate="no"></a></h3>
<p>When a publisher is banned:</p>
<ol>
<li class=""><code>banned</code> set to 1, <code>banned_at</code>, <code>banned_by</code>, <code>ban_reason</code> recorded</li>
<li class="">All active API tokens revoked</li>
<li class="">All their tools set to <code>moderation_status = &#x27;removed&#x27;</code></li>
<li class="">Action logged to audit trail</li>
</ol>
<p>Banned publishers see error on any authenticated API call:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;error&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;code&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;ACCOUNT_BANNED&quot;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token property">&quot;message&quot;</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&quot;Your account has been banned: &lt;reason&gt;&quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="report-resolution-actions">Report Resolution Actions<a href="#report-resolution-actions" class="hash-link" aria-label="Direct link to Report Resolution Actions" title="Direct link to Report Resolution Actions" translate="no"></a></h3>
<table><thead><tr><th>Action</th><th>Effect</th></tr></thead><tbody><tr><td><code>dismiss</code></td><td>Close report, no action taken</td></tr><tr><td><code>warn</code></td><td>Close report, no automated action (manual warning)</td></tr><tr><td><code>remove_tool</code></td><td>Remove the reported tool</td></tr><tr><td><code>ban_publisher</code></td><td>Ban the tool&#x27;s publisher</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="audit-log">Audit Log<a href="#audit-log" class="hash-link" aria-label="Direct link to Audit Log" title="Direct link to Audit Log" translate="no"></a></h3>
<p>All moderation actions are logged:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TABLE</span><span class="token plain"> audit_log </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">INTEGER</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">PRIMARY</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">KEY</span><span class="token plain"> AUTOINCREMENT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">action</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- &#x27;approve_tool&#x27;, &#x27;reject_tool&#x27;, &#x27;ban_publisher&#x27;, etc.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> target_type </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- &#x27;tool&#x27;, &#x27;publisher&#x27;, &#x27;report&#x27;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> target_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> actor_id </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token boolean">NULL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- Who performed the action</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> details </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TEXT</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">-- JSON with additional context</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> created_at </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">TIMESTAMP</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">DEFAULT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CURRENT_TIMESTAMP</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="web-ui-admin-dashboard">Web UI Admin Dashboard<a href="#web-ui-admin-dashboard" class="hash-link" aria-label="Direct link to Web UI Admin Dashboard" title="Direct link to Web UI Admin Dashboard" translate="no"></a></h3>
<p>Moderators and admins see an &quot;Admin Panel&quot; link in their dashboard sidebar leading to:</p>
<ul>
<li class=""><code>/dashboard/admin</code> - Overview with pending counts</li>
<li class=""><code>/dashboard/admin/pending</code> - Pending tools queue</li>
<li class=""><code>/dashboard/admin/publishers</code> - Publisher management</li>
<li class=""><code>/dashboard/admin/reports</code> - Report queue</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="pending-tools-review-page">Pending Tools Review Page<a href="#pending-tools-review-page" class="hash-link" aria-label="Direct link to Pending Tools Review Page" title="Direct link to Pending Tools Review Page" translate="no"></a></h4>
<p>The pending tools page (<code>/dashboard/admin/pending</code>) provides a comprehensive interface for reviewing submitted tools:</p>
<p><strong>Table View:</strong></p>
<ul>
<li class="">Tool name (clickable to open detail modal)</li>
<li class="">Publisher name and category</li>
<li class="">Scrutiny status with expandable warnings</li>
<li class="">Submission date</li>
<li class="">Approve/Reject action buttons</li>
</ul>
<p><strong>Pagination:</strong></p>
<ul>
<li class="">Page number links with ellipsis for large ranges</li>
<li class="">First (<code>«</code>) and Last (<code>»</code>) page buttons</li>
<li class="">Previous/Next navigation</li>
<li class="">&quot;Page X of Y (total)&quot; indicator</li>
</ul>
<p><strong>Tool Detail Modal:</strong></p>
<p>Clicking a tool name opens a draggable modal showing:</p>
<ol>
<li class="">
<p><strong>Scrutiny Warnings</strong> - Yellow warning boxes at the top showing any security or quality concerns from automated analysis, including:</p>
<ul>
<li class="">Check name (e.g., &quot;shell_commands&quot;, &quot;network_access&quot;)</li>
<li class="">Warning message</li>
<li class="">Suggestion for resolution</li>
</ul>
</li>
<li class="">
<p><strong>Description</strong> - Tool&#x27;s description text</p>
</li>
<li class="">
<p><strong>Arguments</strong> - List of tool arguments with flags, variables, and descriptions</p>
</li>
<li class="">
<p><strong>Steps</strong> - Full display of all tool steps:</p>
<ul>
<li class=""><strong>Prompt steps</strong>: Shows provider, output variable, and full prompt content</li>
<li class=""><strong>Code steps</strong>: Shows output variable and code with syntax highlighting (dark theme)</li>
<li class=""><strong>Tool steps</strong>: Shows tool name and arguments</li>
</ul>
</li>
<li class="">
<p><strong>README</strong> - Full README content if provided</p>
</li>
</ol>
<p><strong>Modal Features:</strong></p>
<ul>
<li class="">Draggable by header bar</li>
<li class="">Scrollable content area (header and buttons stay fixed)</li>
<li class="">Approve/Reject buttons in modal footer</li>
<li class="">Dark overlay prevents interaction with page behind</li>
<li class="">Background scroll locked while modal is open</li>
<li class="">Closes with X button or after action</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="creating-the-first-admin">Creating the First Admin<a href="#creating-the-first-admin" class="hash-link" aria-label="Direct link to Creating the First Admin" title="Direct link to Creating the First Admin" translate="no"></a></h3>
<p>After deployment, create the first admin via direct SQL:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">UPDATE</span><span class="token plain"> publishers </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">SET</span><span class="token plain"> role </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;admin&#x27;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">WHERE</span><span class="token plain"> slug </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">&#x27;rob&#x27;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>Subsequent admins can be promoted via the web UI or API.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="implementation-phases">Implementation Phases<a href="#implementation-phases" class="hash-link" aria-label="Direct link to Implementation Phases" title="Direct link to Implementation Phases" translate="no"></a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="phase-1-foundation">Phase 1: Foundation<a href="#phase-1-foundation" class="hash-link" aria-label="Direct link to Phase 1: Foundation" title="Direct link to Phase 1: Foundation" translate="no"></a></h3>
<ul>
<li class="">Define <code>cmdforge.yaml</code> manifest format</li>
<li class="">Implement tool resolution order (local → global → registry)</li>
<li class="">Create CmdForge-Registry repo on Gitea (bootstrap)</li>
<li class="">Add 3-5 example tools to seed the registry</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="phase-2-core-backend">Phase 2: Core Backend<a href="#phase-2-core-backend" class="hash-link" aria-label="Direct link to Phase 2: Core Backend" title="Direct link to Phase 2: Core Backend" translate="no"></a></h3>
<ul>
<li class="">Set up Flask/FastAPI project structure</li>
<li class="">Implement SQLite database schema</li>
<li class="">Build core API endpoints (list, search, get, download)</li>
<li class="">Implement webhook receiver for Gitea sync</li>
<li class="">Set up HMAC verification</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="phase-3-cli-commands">Phase 3: CLI Commands<a href="#phase-3-cli-commands" class="hash-link" aria-label="Direct link to Phase 3: CLI Commands" title="Direct link to Phase 3: CLI Commands" translate="no"></a></h3>
<ul>
<li class=""><code>cmdforge registry search</code></li>
<li class=""><code>cmdforge registry install</code></li>
<li class=""><code>cmdforge registry info</code></li>
<li class=""><code>cmdforge registry browse</code> (TUI)</li>
<li class="">Local index caching</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="phase-4-publishing">Phase 4: Publishing<a href="#phase-4-publishing" class="hash-link" aria-label="Direct link to Phase 4: Publishing" title="Direct link to Phase 4: Publishing" translate="no"></a></h3>
<ul>
<li class="">Publisher registration (web UI)</li>
<li class="">Token management</li>
<li class=""><code>cmdforge registry publish</code> command</li>
<li class="">PR creation via Gitea API</li>
<li class="">CI validation workflows</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="phase-5-project-dependencies">Phase 5: Project Dependencies<a href="#phase-5-project-dependencies" class="hash-link" aria-label="Direct link to Phase 5: Project Dependencies" title="Direct link to Phase 5: Project Dependencies" translate="no"></a></h3>
<ul>
<li class=""><code>cmdforge install</code> (from manifest)</li>
<li class=""><code>cmdforge add</code> command</li>
<li class="">Runtime override application</li>
<li class="">Dependency resolution</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="phase-6-smart-features">Phase 6: Smart Features<a href="#phase-6-smart-features" class="hash-link" aria-label="Direct link to Phase 6: Smart Features" title="Direct link to Phase 6: Smart Features" translate="no"></a></h3>
<ul>
<li class="">SQLite FTS5 search index</li>
<li class="">AI-powered auto-categorization</li>
<li class="">Duplicate/similarity detection</li>
<li class="">Security scanning</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="phase-7-full-web-ui">Phase 7: Full Web UI<a href="#phase-7-full-web-ui" class="hash-link" aria-label="Direct link to Phase 7: Full Web UI" title="Direct link to Phase 7: Full Web UI" translate="no"></a></h3>
<ul>
<li class="">Landing page</li>
<li class="">Tool browsing/search pages</li>
<li class="">Tool detail pages with README rendering</li>
<li class="">Publisher dashboard</li>
<li class="">Documentation/tutorials section</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="phase-8-polish--scale">Phase 8: Polish &amp; Scale<a href="#phase-8-polish--scale" class="hash-link" aria-label="Direct link to Phase 8: Polish &amp; Scale" title="Direct link to Phase 8: Polish &amp; Scale" translate="no"></a></h3>
<ul>
<li class="">Rate limiting</li>
<li class="">Abuse reporting</li>
<li class="">Analytics integration</li>
<li class="">Performance optimization</li>
<li class="">Monitoring/alerting</li>
</ul></div></article><nav class="docusaurus-mt-lg pagination-nav" aria-label="Docs pages"><a class="pagination-nav__link pagination-nav__link--prev" href="/rob/CmdForge/reference/providers"><div class="pagination-nav__sublabel">Previous</div><div class="pagination-nav__label">Provider Setup</div></a><a class="pagination-nav__link pagination-nav__link--next" href="/rob/CmdForge/reference/meta-tools"><div class="pagination-nav__sublabel">Next</div><div class="pagination-nav__label">Meta-Tools</div></a></nav></div></div><div class="col col--3"><div class="tableOfContents_bqdL thin-scrollbar theme-doc-toc-desktop"><ul class="table-of-contents table-of-contents__left-border"><li><a href="#purpose" class="table-of-contents__link toc-highlight">Purpose</a></li><li><a href="#terminology" class="table-of-contents__link toc-highlight">Terminology</a></li><li><a href="#diagram-references" class="table-of-contents__link toc-highlight">Diagram References</a></li><li><a href="#system-overview" class="table-of-contents__link toc-highlight">System Overview</a><ul><li><a href="#pagination" class="table-of-contents__link toc-highlight">Pagination</a></li><li><a href="#input-constraints" class="table-of-contents__link toc-highlight">Input Constraints</a></li><li><a href="#sort-fields-and-indexes" class="table-of-contents__link toc-highlight">Sort Fields and Indexes</a></li><li><a href="#tags-endpoint" class="table-of-contents__link toc-highlight">Tags Endpoint</a></li><li><a href="#advanced-search" class="table-of-contents__link toc-highlight">Advanced Search</a></li><li><a href="#api-version-compatibility" class="table-of-contents__link toc-highlight">API Version Compatibility</a></li></ul></li><li><a href="#source-of-truth" class="table-of-contents__link toc-highlight">Source of Truth</a></li><li><a href="#namespacing-and-paths" class="table-of-contents__link toc-highlight">Namespacing and Paths</a><ul><li><a href="#namespace-identity" class="table-of-contents__link toc-highlight">Namespace Identity</a></li></ul></li><li><a href="#tool-format-registry--local" class="table-of-contents__link toc-highlight">Tool Format (Registry == Local)</a><ul><li><a href="#attribution-and-source-fields" class="table-of-contents__link toc-highlight">Attribution and Source Fields</a></li></ul></li><li><a href="#collections" class="table-of-contents__link toc-highlight">Collections</a><ul><li><a href="#collection-structure" class="table-of-contents__link toc-highlight">Collection Structure</a></li><li><a href="#collections-api" class="table-of-contents__link toc-highlight">Collections API</a></li><li><a href="#cli-commands" class="table-of-contents__link toc-highlight">CLI Commands</a></li><li><a href="#admin-collections-api" class="table-of-contents__link toc-highlight">Admin Collections API</a></li></ul></li><li><a href="#versioning-and-immutability" class="table-of-contents__link toc-highlight">Versioning and Immutability</a><ul><li><a href="#yank-policy" class="table-of-contents__link toc-highlight">Yank Policy</a></li><li><a href="#version-format" class="table-of-contents__link toc-highlight">Version Format</a></li><li><a href="#version-constraints" class="table-of-contents__link toc-highlight">Version Constraints</a></li><li><a href="#version-resolution-rules" class="table-of-contents__link toc-highlight">Version Resolution Rules</a></li><li><a href="#prerelease-handling" class="table-of-contents__link toc-highlight">Prerelease Handling</a></li><li><a href="#download-endpoint-version-selection" class="table-of-contents__link toc-highlight">Download Endpoint Version Selection</a></li></ul></li><li><a href="#tool-resolution-order" class="table-of-contents__link toc-highlight">Tool Resolution Order</a><ul><li><a href="#official-namespace" class="table-of-contents__link toc-highlight">Official Namespace</a></li></ul></li><li><a href="#auto-fetch-behavior" class="table-of-contents__link toc-highlight">Auto-Fetch Behavior</a><ul><li><a href="#wrapper-script-collisions" class="table-of-contents__link toc-highlight">Wrapper Script Collisions</a></li></ul></li><li><a href="#project-manifest-cmdforgeyaml" class="table-of-contents__link toc-highlight">Project Manifest (cmdforge.yaml)</a></li><li><a href="#cli-config-and-tokens" class="table-of-contents__link toc-highlight">CLI Config and Tokens</a></li><li><a href="#publishing-and-auth" class="table-of-contents__link toc-highlight">Publishing and Auth</a><ul><li><a href="#publish-idempotency-and-edge-cases" class="table-of-contents__link toc-highlight">Publish Idempotency and Edge Cases</a></li></ul></li><li><a href="#publisher-registration" class="table-of-contents__link toc-highlight">Publisher Registration</a><ul><li><a href="#authentication-security" class="table-of-contents__link toc-highlight">Authentication Security</a></li><li><a href="#token-scopes-and-authorization" class="table-of-contents__link toc-highlight">Token Scopes and Authorization</a></li><li><a href="#web-session-security" class="table-of-contents__link toc-highlight">Web Session Security</a></li></ul></li><li><a href="#cli-commands-reference" class="table-of-contents__link toc-highlight">CLI Commands Reference</a><ul><li><a href="#registry-commands" class="table-of-contents__link toc-highlight">Registry Commands</a></li><li><a href="#project-commands" class="table-of-contents__link toc-highlight">Project Commands</a></li><li><a href="#config-commands" class="table-of-contents__link toc-highlight">Config Commands</a></li><li><a href="#flags-available-on-most-commands" class="table-of-contents__link toc-highlight">Flags available on most commands</a></li></ul></li><li><a href="#publish-state-tracking" class="table-of-contents__link toc-highlight">Publish State Tracking</a><ul><li><a href="#local-state-storage" class="table-of-contents__link toc-highlight">Local State Storage</a></li><li><a href="#visual-indicators" class="table-of-contents__link toc-highlight">Visual Indicators</a></li><li><a href="#automatic-status-sync" class="table-of-contents__link toc-highlight">Automatic Status Sync</a></li><li><a href="#manual-sync" class="table-of-contents__link toc-highlight">Manual Sync</a></li><li><a href="#hash-computation" class="table-of-contents__link toc-highlight">Hash Computation</a></li><li><a href="#api-endpoint" class="table-of-contents__link toc-highlight">API Endpoint</a></li></ul></li><li><a href="#webhooks-and-security" class="table-of-contents__link toc-highlight">Webhooks and Security</a><ul><li><a href="#hmac-verification" class="table-of-contents__link toc-highlight">HMAC Verification</a></li><li><a href="#replay-protection" class="table-of-contents__link toc-highlight">Replay Protection</a></li><li><a href="#sync-job-locking" class="table-of-contents__link toc-highlight">Sync Job Locking</a></li><li><a href="#atomic-sync-strategy" class="table-of-contents__link toc-highlight">Atomic Sync Strategy</a></li><li><a href="#error-handling" class="table-of-contents__link toc-highlight">Error Handling</a></li></ul></li><li><a href="#automated-ci-validation" class="table-of-contents__link toc-highlight">Automated CI Validation</a></li><li><a href="#registry-repository-structure" class="table-of-contents__link toc-highlight">Registry Repository Structure</a></li><li><a href="#download-stats" class="table-of-contents__link toc-highlight">Download Stats</a><ul><li><a href="#counting-methodology" class="table-of-contents__link toc-highlight">Counting Methodology</a></li><li><a href="#client-id-generation" class="table-of-contents__link toc-highlight">Client ID Generation</a></li><li><a href="#privacy-considerations" class="table-of-contents__link toc-highlight">Privacy Considerations</a></li><li><a href="#async-stats-strategy" class="table-of-contents__link toc-highlight">Async Stats Strategy</a></li></ul></li><li><a href="#search" class="table-of-contents__link toc-highlight">Search</a></li><li><a href="#api-caching-strategy" class="table-of-contents__link toc-highlight">API Caching Strategy</a><ul><li><a href="#cache-headers" class="table-of-contents__link toc-highlight">Cache Headers</a></li><li><a href="#etag-implementation" class="table-of-contents__link toc-highlight">ETag Implementation</a></li><li><a href="#db-vs-repo-read-strategy" class="table-of-contents__link toc-highlight">DB vs Repo Read Strategy</a></li><li><a href="#staleness-detection" class="table-of-contents__link toc-highlight">Staleness Detection</a></li></ul></li><li><a href="#error-model" class="table-of-contents__link toc-highlight">Error Model</a><ul><li><a href="#response-envelopes" class="table-of-contents__link toc-highlight">Response Envelopes</a></li><li><a href="#error-codes" class="table-of-contents__link toc-highlight">Error Codes</a></li></ul></li><li><a href="#error-scenarios-and-fallbacks" class="table-of-contents__link toc-highlight">Error Scenarios and Fallbacks</a><ul><li><a href="#cli-error-handling" class="table-of-contents__link toc-highlight">CLI Error Handling</a></li><li><a href="#validation-failure-details" class="table-of-contents__link toc-highlight">Validation Failure Details</a></li><li><a href="#dependency-resolution-failures" class="table-of-contents__link toc-highlight">Dependency Resolution Failures</a></li><li><a href="#graceful-degradation" class="table-of-contents__link toc-highlight">Graceful Degradation</a></li></ul></li><li><a href="#ux-requirements-clitui" class="table-of-contents__link toc-highlight">UX Requirements (CLI/TUI)</a><ul><li><a href="#publishing-ux" class="table-of-contents__link toc-highlight">Publishing UX</a></li><li><a href="#progress-indicators" class="table-of-contents__link toc-highlight">Progress Indicators</a></li><li><a href="#tui-browse" class="table-of-contents__link toc-highlight">TUI Browse</a></li><li><a href="#project-initialization" class="table-of-contents__link toc-highlight">Project Initialization</a></li><li><a href="#accessibility" class="table-of-contents__link toc-highlight">Accessibility</a></li></ul></li><li><a href="#offline-cache" class="table-of-contents__link toc-highlight">Offline Cache</a><ul><li><a href="#index-integrity" class="table-of-contents__link toc-highlight">Index Integrity</a></li></ul></li><li><a href="#web-ui-vision" class="table-of-contents__link toc-highlight">Web UI Vision</a><ul><li><a href="#readme-security" class="table-of-contents__link toc-highlight">README Security</a></li></ul></li><li><a href="#registry-curation-system" class="table-of-contents__link toc-highlight">Registry Curation System</a><ul><li><a href="#roles-and-permissions" class="table-of-contents__link toc-highlight">Roles and Permissions</a></li><li><a href="#tool-visibility" class="table-of-contents__link toc-highlight">Tool Visibility</a></li><li><a href="#moderation-workflow" class="table-of-contents__link toc-highlight">Moderation Workflow</a></li><li><a href="#admin-api-endpoints" class="table-of-contents__link toc-highlight">Admin API Endpoints</a></li><li><a href="#ban-behavior" class="table-of-contents__link toc-highlight">Ban Behavior</a></li><li><a href="#report-resolution-actions" class="table-of-contents__link toc-highlight">Report Resolution Actions</a></li><li><a href="#audit-log" class="table-of-contents__link toc-highlight">Audit Log</a></li><li><a href="#web-ui-admin-dashboard" class="table-of-contents__link toc-highlight">Web UI Admin Dashboard</a></li><li><a href="#creating-the-first-admin" class="table-of-contents__link toc-highlight">Creating the First Admin</a></li></ul></li><li><a href="#implementation-phases" class="table-of-contents__link toc-highlight">Implementation Phases</a><ul><li><a href="#phase-1-foundation" class="table-of-contents__link toc-highlight">Phase 1: Foundation</a></li><li><a href="#phase-2-core-backend" class="table-of-contents__link toc-highlight">Phase 2: Core Backend</a></li><li><a href="#phase-3-cli-commands" class="table-of-contents__link toc-highlight">Phase 3: CLI Commands</a></li><li><a href="#phase-4-publishing" class="table-of-contents__link toc-highlight">Phase 4: Publishing</a></li><li><a href="#phase-5-project-dependencies" class="table-of-contents__link toc-highlight">Phase 5: Project Dependencies</a></li><li><a href="#phase-6-smart-features" class="table-of-contents__link toc-highlight">Phase 6: Smart Features</a></li><li><a href="#phase-7-full-web-ui" class="table-of-contents__link toc-highlight">Phase 7: Full Web UI</a></li><li><a href="#phase-8-polish--scale" class="table-of-contents__link toc-highlight">Phase 8: Polish &amp; Scale</a></li></ul></li></ul></div></div></div></div></main></div></div></div><footer class="theme-layout-footer footer footer--dark"><div class="container container-fluid"><div class="row footer__links"><div class="theme-layout-footer-column col footer__col"><div class="footer__title">Docs</div><ul class="footer__items clean-list"><li class="footer__item"><a class="footer__link-item" href="/rob/CmdForge/">Overview</a></li></ul></div><div class="theme-layout-footer-column col footer__col"><div class="footer__title">More</div><ul class="footer__items clean-list"><li class="footer__item"><a href="https://gitea.brrd.tech/rob/CmdForge" target="_blank" rel="noopener noreferrer" class="footer__link-item">Gitea<svg width="13.5" height="13.5" aria-label="(opens in new tab)" class="iconExternalLink_nPIU"><use href="#theme-svg-external-link"></use></svg></a></li></ul></div></div><div class="footer__bottom text--center"><div class="footer__copyright">CmdForge Documentation</div></div></div></footer></div>
</body>
</html>