Rename project from SmartTools to CmdForge

Major rename operation:
- Rename Python package: smarttools -> cmdforge
- Update CLI entry point: smarttools -> cmdforge
- Update all imports and module references
- Update pyproject.toml with new name, URLs, entry point
- Update all HTML templates with new branding
- Update documentation (CLAUDE.md, README.md, docs/*, wiki/*)
- Update environment variables:
  - SMARTTOOLS_ENV -> CMDFORGE_ENV
  - SMARTTOOLS_REGISTRY_DB -> CMDFORGE_REGISTRY_DB
  - SMARTTOOLS_TOKEN -> CMDFORGE_TOKEN
  - SMARTTOOLS_REGISTRY_* -> CMDFORGE_REGISTRY_*
- Update Dockerfile and docker-compose.yml
- Update tests to use new package name
- Update scripts and examples
- Update package.json and tailwind.config.js

All 158 unit tests pass. The CLI is working correctly with the
new cmdforge command.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
rob
2026-01-02 22:15:55 -04:00
co-authored by Claude Opus 4.5
parent 2939dd806b
commit 497fe87fc5
113 changed files with 822 additions and 822 deletions
@@ -0,0 +1,284 @@
{% extends "base.html" %}
{% from "components/callouts.html" import warning, info %}
{% block title %}{{ tool.owner }}/{{ tool.name }} - CmdForge Registry{% endblock %}
{% block meta_description %}{{ tool.description or 'A CmdForge command-line tool' }}{% endblock %}
{% block og_title %}{{ tool.owner }}/{{ tool.name }}{% endblock %}
{% block og_description %}{{ tool.description or 'A CmdForge command-line tool' }}{% endblock %}
{% block content %}
<div class="bg-gray-50 min-h-screen">
<!-- Breadcrumb -->
<div class="bg-white border-b border-gray-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<nav class="flex items-center text-sm text-gray-500" aria-label="Breadcrumb">
<a href="{{ url_for('web.tools') }}" class="hover:text-gray-700">Tools</a>
<svg class="w-4 h-4 mx-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
</svg>
<a href="{{ url_for('web.publisher', slug=tool.owner) }}" class="hover:text-gray-700">{{ tool.owner }}</a>
<svg class="w-4 h-4 mx-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
</svg>
<span class="text-gray-900 font-medium">{{ tool.name }}</span>
</nav>
</div>
</div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="lg:grid lg:grid-cols-3 lg:gap-8">
<!-- Main Content -->
<main class="lg:col-span-2">
<!-- Tool Header -->
<div class="bg-white rounded-lg border border-gray-200 p-6 mb-6">
<div class="flex items-start justify-between">
<div class="flex items-center">
<div class="w-16 h-16 bg-indigo-100 rounded-lg flex items-center justify-center">
<svg class="w-8 h-8 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
</div>
<div class="ml-4">
<h1 class="text-2xl font-bold text-gray-900">{{ tool.name }}</h1>
<p class="text-gray-500">
by <a href="{{ url_for('web.publisher', slug=tool.owner) }}" class="text-indigo-600 hover:text-indigo-800">{{ tool.owner }}</a>
</p>
</div>
</div>
{% if tool.category %}
<span class="px-3 py-1 text-xs font-medium text-cyan-700 bg-cyan-100 rounded-full">
{{ tool.category }}
</span>
{% endif %}
</div>
{% if tool.description %}
<p class="mt-4 text-gray-600">{{ tool.description }}</p>
{% endif %}
{% if tool.tags %}
<div class="mt-4 flex flex-wrap gap-2">
{% for tag in tool.tags.split(',') %}
<span class="px-2 py-1 text-xs text-gray-600 bg-gray-100 rounded">{{ tag.strip() }}</span>
{% endfor %}
</div>
{% endif %}
</div>
<!-- Deprecation Warning -->
{% if tool.deprecated %}
{{ warning(
title='This tool is deprecated',
content=tool.deprecated_message or 'This tool is no longer maintained.'
) }}
{% if tool.replacement %}
{{ info(
title='Recommended replacement',
content='Consider using <a href="' ~ url_for('web.tool_detail', owner=tool.replacement.split('/')[0], name=tool.replacement.split('/')[1]) ~ '" class="underline">' ~ tool.replacement ~ '</a> instead.'
) }}
{% endif %}
{% endif %}
<!-- README Content -->
<div class="bg-white rounded-lg border border-gray-200 p-6">
<div class="prose prose-indigo max-w-none">
{% if tool.readme %}
{{ tool.readme_html|safe }}
{% else %}
<h2>About {{ tool.name }}</h2>
<p>{{ tool.description or 'No additional documentation available.' }}</p>
<h2>Installation</h2>
<pre><code>cmdforge install {{ tool.owner }}/{{ tool.name }}</code></pre>
<h2>Usage</h2>
<pre><code>{{ tool.name }} --help</code></pre>
{% endif %}
</div>
</div>
</main>
<!-- Sidebar -->
<aside class="mt-8 lg:mt-0">
<div class="sticky top-4 space-y-6">
<!-- Install Card -->
<div class="bg-white rounded-lg border border-gray-200 p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Install</h3>
<div class="flex items-center bg-gray-100 rounded-lg p-3 group">
<span class="text-gray-400 mr-2 select-none">$</span>
<code id="install-cmd" class="flex-1 text-sm text-gray-800 font-mono overflow-x-auto">cmdforge install {{ tool.owner }}/{{ tool.name }}</code>
<button type="button"
onclick="copyInstall()"
class="ml-2 p-1.5 text-gray-400 hover:text-gray-600"
aria-label="Copy install command">
<svg id="copy-icon" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
</svg>
<svg id="check-icon" class="w-4 h-4 hidden text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>
</button>
</div>
<button type="button"
onclick="submitReport('{{ tool.owner }}', '{{ tool.name }}')"
class="mt-4 w-full inline-flex items-center justify-center px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-md hover:bg-red-700">
Report Tool
</button>
</div>
<!-- Versions -->
<div class="bg-white rounded-lg border border-gray-200 p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Versions</h3>
<ul class="space-y-2">
{% for version in versions %}
<li class="flex items-center justify-between">
<a href="{{ url_for('web.tool_version', owner=tool.owner, name=tool.name, version=version.version) }}"
class="text-sm {{ 'font-medium text-indigo-600' if version.version == tool.version else 'text-gray-600 hover:text-indigo-600' }}">
v{{ version.version }}
{% if version.version == tool.version %}
<span class="ml-1 text-xs text-green-600">(current)</span>
{% endif %}
</a>
<span class="text-xs text-gray-400">{{ version.published_at|timeago }}</span>
</li>
{% else %}
<li class="text-sm text-gray-500">v{{ tool.version }}</li>
{% endfor %}
</ul>
</div>
<!-- Stats -->
<div class="bg-white rounded-lg border border-gray-200 p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Stats</h3>
<dl class="space-y-3">
<div class="flex items-center justify-between">
<dt class="text-sm text-gray-500">Downloads</dt>
<dd class="text-sm font-medium text-gray-900">{{ tool.downloads|format_number }}</dd>
</div>
<div class="flex items-center justify-between">
<dt class="text-sm text-gray-500">Published</dt>
<dd class="text-sm font-medium text-gray-900">{{ tool.published_at|timeago }}</dd>
</div>
{% if tool.category %}
<div class="flex items-center justify-between">
<dt class="text-sm text-gray-500">Category</dt>
<dd>
<a href="{{ url_for('web.category', name=tool.category) }}"
class="text-sm font-medium text-indigo-600 hover:text-indigo-800">
{{ tool.category }}
</a>
</dd>
</div>
{% endif %}
</dl>
</div>
<!-- Publisher -->
<div class="bg-white rounded-lg border border-gray-200 p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Publisher</h3>
<a href="{{ url_for('web.publisher', slug=tool.owner) }}"
class="flex items-center group">
<div class="w-10 h-10 bg-gray-200 rounded-full flex items-center justify-center">
<span class="text-gray-600 font-medium">{{ tool.owner[0]|upper }}</span>
</div>
<div class="ml-3">
<p class="text-sm font-medium text-gray-900 group-hover:text-indigo-600">
{{ publisher.display_name if publisher else tool.owner }}
</p>
<p class="text-xs text-gray-500">@{{ tool.owner }}</p>
</div>
{% if publisher and publisher.verified %}
<svg class="ml-auto w-5 h-5 text-blue-500" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg>
{% endif %}
</a>
</div>
<!-- Report Tool -->
<div class="text-center">
<button type="button"
onclick="submitReport('{{ tool.owner }}', '{{ tool.name }}')"
class="text-sm text-gray-500 hover:text-red-600 transition-colors">
Report this tool
</button>
</div>
</div>
</aside>
</div>
</div>
</div>
<!-- Report Modal -->
<div id="report-modal" class="hidden fixed inset-0 z-50 overflow-y-auto" aria-modal="true" role="dialog">
<div class="min-h-screen px-4 text-center">
<div class="fixed inset-0 bg-black bg-opacity-50 transition-opacity" onclick="closeReportModal()"></div>
<div class="inline-block w-full max-w-md my-8 text-left align-middle bg-white shadow-xl rounded-lg">
<form action="/api/v1/reports" method="POST" id="report-form">
<div class="p-6">
<h3 class="text-lg font-semibold text-gray-900">Report Tool</h3>
<p class="mt-2 text-sm text-gray-600">
Help us maintain a safe registry by reporting tools that violate our guidelines.
</p>
<input type="hidden" name="tool_owner" id="report-owner" value="{{ tool.owner }}">
<input type="hidden" name="tool_name" id="report-name" value="{{ tool.name }}">
<div class="mt-6 space-y-4">
<div>
<label for="reason" class="block text-sm font-medium text-gray-700 mb-1">Reason</label>
<select name="reason" id="reason" required
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500">
<option value="">Select a reason...</option>
<option value="spam">Spam</option>
<option value="malicious">Malicious</option>
<option value="duplicate">Duplicate</option>
<option value="inappropriate">Inappropriate</option>
<option value="other">Other</option>
</select>
</div>
<div>
<label for="description" class="block text-sm font-medium text-gray-700 mb-1">Description (optional)</label>
<textarea name="description" id="description" rows="3"
placeholder="Provide additional context"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 resize-none"></textarea>
</div>
</div>
</div>
<div class="bg-gray-50 px-6 py-4 rounded-b-lg flex justify-end gap-3">
<button type="button"
onclick="closeReportModal()"
class="px-4 py-2 text-sm font-medium text-gray-700 hover:text-gray-900">
Cancel
</button>
<button type="submit"
class="px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-md hover:bg-red-700">
Submit Report
</button>
</div>
</form>
</div>
</div>
</div>
<script>
function copyInstall() {
const cmd = 'cmdforge install {{ tool.owner }}/{{ tool.name }}';
navigator.clipboard.writeText(cmd).then(() => {
const copyIcon = document.getElementById('copy-icon');
const checkIcon = document.getElementById('check-icon');
copyIcon.classList.add('hidden');
checkIcon.classList.remove('hidden');
setTimeout(() => {
copyIcon.classList.remove('hidden');
checkIcon.classList.add('hidden');
}, 2000);
});
}
</script>
{% endblock %}