Prepare CmdForge 0.2.0 for PyPI
This commit is contained in:
parent
ca6135ac72
commit
71dfd86c3e
|
|
@ -4,6 +4,14 @@ All notable changes to CmdForge will be documented in this file.
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.2.0] - 2026-07-21
|
||||||
|
|
||||||
|
First public Python package release. This release brings the current CmdForge
|
||||||
|
application to PyPI, including provider routing and provenance, MCP client and
|
||||||
|
server support, provider-attached skills, delegated tools, contracts and
|
||||||
|
preflight analysis, regression evidence, quality scoring, registry integrity,
|
||||||
|
project-owned tools, agent discovery commands, and local usage suggestions.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
#### System Dependencies
|
#### System Dependencies
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Rob
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
# CmdForge
|
||||||
|
|
||||||
|
**Turn useful AI workflows into commands you own.**
|
||||||
|
|
||||||
|
CmdForge is a personal tool-building environment for the command line. A tool
|
||||||
|
can combine prompts, Python, other CmdForge tools, and Model Context Protocol
|
||||||
|
(MCP) calls, then behave like an ordinary Unix command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat meeting.txt | meeting-decisions
|
||||||
|
git diff --staged | review-change
|
||||||
|
cmdforge run classify-private --no-fallback --require-local
|
||||||
|
```
|
||||||
|
|
||||||
|
Tools are readable YAML, stored either in `~/.cmdforge/` for personal use or
|
||||||
|
`./.cmdforge/` when they belong to a project. You choose the provider at build
|
||||||
|
time or runtime: a local Ollama model, an installed coding CLI, or an
|
||||||
|
OpenAI-compatible API.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
CmdForge requires Python 3.10 or newer. `pipx` is recommended for a personal
|
||||||
|
command-line application:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pipx install "cmdforge[mcp,pty]"
|
||||||
|
```
|
||||||
|
|
||||||
|
Or install it in a virtual environment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pip install "cmdforge[mcp,pty]"
|
||||||
|
```
|
||||||
|
|
||||||
|
The base `cmdforge` package includes the CLI and desktop tool builder. The
|
||||||
|
`mcp` extra adds MCP client/server support, while `pty` adds interactive CLI
|
||||||
|
providers.
|
||||||
|
|
||||||
|
Verify the installation and discover providers already present on the machine:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmdforge --version
|
||||||
|
cmdforge providers discover
|
||||||
|
cmdforge providers discover --add
|
||||||
|
```
|
||||||
|
|
||||||
|
## Begin with One Useful Command
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmdforge create explain --prompt "Explain this clearly for a beginner: {input}"
|
||||||
|
echo "def area(r): return 3.14159 * r * r" | cmdforge run explain
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the deterministic mock provider while developing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "sample input" | cmdforge run explain --provider mock
|
||||||
|
cmdforge inspect explain
|
||||||
|
```
|
||||||
|
|
||||||
|
For AI-assisted creation, install CmdForge's official `forge-tool`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmdforge registry install official/forge-tool
|
||||||
|
|
||||||
|
echo "Create a tool that extracts decisions and owners from meeting notes" \
|
||||||
|
| forge-tool --name meeting-decisions --project
|
||||||
|
```
|
||||||
|
|
||||||
|
## Four Kinds of Step
|
||||||
|
|
||||||
|
- **Prompt steps** call a selected AI provider.
|
||||||
|
- **Code steps** perform deterministic Python transformations.
|
||||||
|
- **Tool steps** compose existing CmdForge tools and delegated contexts.
|
||||||
|
- **MCP steps** call tools exposed by external MCP servers.
|
||||||
|
|
||||||
|
Contracts, deterministic preflight, regression baselines, schema compatibility,
|
||||||
|
and evidence-based quality scores help distinguish “worked once” from a tool
|
||||||
|
you can maintain.
|
||||||
|
|
||||||
|
## Use CmdForge from Coding Agents
|
||||||
|
|
||||||
|
CmdForge can act as an MCP server for Codex and Claude Code. It is closed by
|
||||||
|
default: configuring a host does not expose any tools until you add an explicit
|
||||||
|
allowlist to `~/.cmdforge/mcp.yaml`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmdforge mcp configure codex --dry-run
|
||||||
|
cmdforge mcp configure codex
|
||||||
|
|
||||||
|
cmdforge mcp configure claude-code --scope project --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
Agents can also discover the local and remote catalogs without reading prompt
|
||||||
|
or code bodies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmdforge list --json --filter "commit message" --limit 10
|
||||||
|
cmdforge registry search "release notes" --json --limit 5
|
||||||
|
git diff | cmdforge run-once "Summarize this change: {input}"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Privacy and Provenance
|
||||||
|
|
||||||
|
Fallback is a data-movement decision. Sensitive callers can require a local
|
||||||
|
provider, particular capabilities, and verified runtime identity:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmdforge run incident-summary \
|
||||||
|
--provider local-reasoner \
|
||||||
|
--no-fallback \
|
||||||
|
--require-local \
|
||||||
|
--require-capability structured-json \
|
||||||
|
--data-classification private \
|
||||||
|
--require-model-identity \
|
||||||
|
--result-envelope json
|
||||||
|
```
|
||||||
|
|
||||||
|
The result envelope reports the requested provider, actual provider, attempted
|
||||||
|
fallback chain, model, digest, and locality. Those fields are produced by
|
||||||
|
CmdForge rather than invented by the model.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
- [Documentation](https://cmdforge.brrd.tech/docs)
|
||||||
|
- [MCP and coding agents](https://cmdforge.brrd.tech/docs/mcp-overview)
|
||||||
|
- [Providers and privacy policy](https://cmdforge.brrd.tech/docs/providers)
|
||||||
|
- [Contracts and quality evidence](https://cmdforge.brrd.tech/docs/contracts-quality)
|
||||||
|
- [Registry](https://cmdforge.brrd.tech/tools)
|
||||||
|
- [Source and issues](https://gitea.brrd.tech/rob/CmdForge)
|
||||||
|
|
||||||
|
CmdForge is beta software, distributed under the MIT License. Review generated
|
||||||
|
tools and third-party registry tools as you would any other executable code.
|
||||||
30
README.md
30
README.md
|
|
@ -55,16 +55,14 @@ That's it - you just used AI to explain itself. The `eli5` tool uses a free mode
|
||||||
For regular use, install natively:
|
For regular use, install natively:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone and install
|
# Install the application with MCP and interactive-provider support
|
||||||
git clone https://gitea.brrd.tech/rob/CmdForge.git
|
pipx install "cmdforge[mcp,pty]"
|
||||||
cd CmdForge
|
|
||||||
pip install -e .
|
|
||||||
|
|
||||||
# Ensure ~/.local/bin is in PATH
|
# Ensure ~/.local/bin is in PATH
|
||||||
export PATH="$HOME/.local/bin:$PATH"
|
export PATH="$HOME/.local/bin:$PATH"
|
||||||
|
|
||||||
# Install an AI provider (interactive guide)
|
# Discover AI CLIs, API keys, and Ollama models already on this machine
|
||||||
cmdforge providers install
|
cmdforge providers discover --add
|
||||||
|
|
||||||
# Launch the GUI
|
# Launch the GUI
|
||||||
cmdforge
|
cmdforge
|
||||||
|
|
@ -75,12 +73,24 @@ cmdforge create summarize
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Native Install
|
### From PyPI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pipx install "cmdforge[mcp,pty]"
|
||||||
|
```
|
||||||
|
|
||||||
|
Or install into an active virtual environment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pip install "cmdforge[mcp,pty]"
|
||||||
|
```
|
||||||
|
|
||||||
|
### From Source
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://gitea.brrd.tech/rob/CmdForge.git
|
git clone https://gitea.brrd.tech/rob/CmdForge.git
|
||||||
cd CmdForge
|
cd CmdForge
|
||||||
pip install -e .
|
python -m pip install -e ".[mcp,pty]"
|
||||||
```
|
```
|
||||||
|
|
||||||
### With Development Dependencies
|
### With Development Dependencies
|
||||||
|
|
@ -94,7 +104,7 @@ pip install -e ".[dev]"
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
- Python 3.10+
|
- Python 3.10+
|
||||||
- At least one AI CLI tool installed (see [Provider Setup](docs/reference/providers.md))
|
- A configured provider for live AI calls; the built-in mock works without one
|
||||||
- PySide6 (included automatically - requires display server on Linux)
|
- PySide6 (included automatically - requires display server on Linux)
|
||||||
|
|
||||||
### Post-Install
|
### Post-Install
|
||||||
|
|
@ -195,7 +205,7 @@ cmdforge usage enable # Begin recording tool names in shell pipe
|
||||||
cmdforge usage suggestions # Show frequent pipelines
|
cmdforge usage suggestions # Show frequent pipelines
|
||||||
cmdforge usage clear # Delete all local usage history
|
cmdforge usage clear # Delete all local usage history
|
||||||
|
|
||||||
# Model Context Protocol (optional: pip install -e ".[mcp]")
|
# Model Context Protocol (optional: python -m pip install "cmdforge[mcp]")
|
||||||
cmdforge mcp add local --command npx --arg=-y --arg @scope/server
|
cmdforge mcp add local --command npx --arg=-y --arg @scope/server
|
||||||
cmdforge mcp add remote --transport streamable-http --url https://example.com/mcp
|
cmdforge mcp add remote --transport streamable-http --url https://example.com/mcp
|
||||||
cmdforge mcp connect remote
|
cmdforge mcp connect remote
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
# Publishing CmdForge to PyPI
|
||||||
|
|
||||||
|
PyPI releases are public and immutable. A release version must never be reused
|
||||||
|
for different bytes, even if an upload contains a mistake.
|
||||||
|
|
||||||
|
## One-time account setup
|
||||||
|
|
||||||
|
1. Create or sign in to an account at <https://pypi.org/>.
|
||||||
|
2. Verify the account email address.
|
||||||
|
3. Enable two-factor authentication and store the recovery codes in the
|
||||||
|
password manager and an offline recovery location.
|
||||||
|
4. For the first upload only, create an account-scoped API token. PyPI cannot
|
||||||
|
create a project-scoped token until the project exists.
|
||||||
|
5. Do not put a PyPI token in Git, `.pypirc`, chat, command arguments, or shell
|
||||||
|
history. Let Twine prompt for it interactively.
|
||||||
|
|
||||||
|
After the first successful upload, immediately revoke the account-scoped token
|
||||||
|
and create a new token restricted to the `cmdforge` project.
|
||||||
|
|
||||||
|
## Prepare and validate a release
|
||||||
|
|
||||||
|
Update the version in both `pyproject.toml` and `src/cmdforge/__init__.py`, then
|
||||||
|
add the release to `CHANGELOG.md`. From a clean checkout:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pip install -e '.[release]'
|
||||||
|
pytest tests/ -m "not integration"
|
||||||
|
python -m build
|
||||||
|
python -m twine check dist/*
|
||||||
|
```
|
||||||
|
|
||||||
|
Inspect the wheel and source archive, then install the wheel into a clean
|
||||||
|
temporary virtual environment and exercise both entry points. Do not upload an
|
||||||
|
artifact that was built before the release commit.
|
||||||
|
|
||||||
|
## Upload
|
||||||
|
|
||||||
|
Run Twine interactively so the token is not recorded in shell history:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m twine upload dist/*
|
||||||
|
```
|
||||||
|
|
||||||
|
When prompted, use `__token__` as the username and paste the API token as the
|
||||||
|
password. Once uploaded, verify the public project and install from PyPI in a
|
||||||
|
new environment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m venv /tmp/cmdforge-pypi-check
|
||||||
|
/tmp/cmdforge-pypi-check/bin/pip install 'cmdforge[mcp,pty]'
|
||||||
|
/tmp/cmdforge-pypi-check/bin/cmdforge --version
|
||||||
|
/tmp/cmdforge-pypi-check/bin/cmdforge --help
|
||||||
|
```
|
||||||
|
|
||||||
|
Tag and push only the commit whose artifacts were published:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git tag -a v0.2.0 -m "CmdForge 0.2.0"
|
||||||
|
git push origin main
|
||||||
|
git push origin v0.2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
If an upload is wrong, fix it, increment the version, rebuild, and publish a
|
||||||
|
new release. Never delete and reuse the version number.
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools>=61.0", "wheel"]
|
requires = ["setuptools>=77.0", "wheel"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "cmdforge"
|
name = "cmdforge"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
description = "Build custom AI-powered CLI commands in YAML"
|
description = "Build, compose, and share AI-powered command-line tools you own"
|
||||||
readme = "README.md"
|
readme = "PYPI_README.md"
|
||||||
license = {text = "MIT"}
|
license = "MIT"
|
||||||
|
license-files = ["LICENSE"]
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
authors = [
|
authors = [
|
||||||
{name = "Rob"}
|
{name = "Rob"}
|
||||||
|
|
@ -18,7 +19,6 @@ classifiers = [
|
||||||
"Environment :: Console",
|
"Environment :: Console",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"Intended Audience :: System Administrators",
|
"Intended Audience :: System Administrators",
|
||||||
"License :: OSI Approved :: MIT License",
|
|
||||||
"Operating System :: POSIX :: Linux",
|
"Operating System :: POSIX :: Linux",
|
||||||
"Operating System :: MacOS",
|
"Operating System :: MacOS",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
|
|
@ -45,6 +45,10 @@ dev = [
|
||||||
"pytest-cov>=4.0",
|
"pytest-cov>=4.0",
|
||||||
"tomli>=1.1; python_version < '3.11'",
|
"tomli>=1.1; python_version < '3.11'",
|
||||||
]
|
]
|
||||||
|
release = [
|
||||||
|
"build>=1.2",
|
||||||
|
"twine>=6.0",
|
||||||
|
]
|
||||||
registry = [
|
registry = [
|
||||||
"Flask>=2.3",
|
"Flask>=2.3",
|
||||||
"argon2-cffi>=21.0",
|
"argon2-cffi>=21.0",
|
||||||
|
|
@ -81,6 +85,7 @@ Homepage = "https://cmdforge.brrd.tech"
|
||||||
Documentation = "https://cmdforge.brrd.tech/docs"
|
Documentation = "https://cmdforge.brrd.tech/docs"
|
||||||
Repository = "https://gitea.brrd.tech/rob/CmdForge.git"
|
Repository = "https://gitea.brrd.tech/rob/CmdForge.git"
|
||||||
Issues = "https://gitea.brrd.tech/rob/CmdForge/issues"
|
Issues = "https://gitea.brrd.tech/rob/CmdForge/issues"
|
||||||
|
Changelog = "https://gitea.brrd.tech/rob/CmdForge/src/branch/main/CHANGELOG.md"
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
where = ["src"]
|
where = ["src"]
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
"""CmdForge - A lightweight personal tool builder for AI-powered CLI commands."""
|
"""CmdForge - A lightweight personal tool builder for AI-powered CLI commands."""
|
||||||
|
|
||||||
__version__ = "0.1.0"
|
__version__ = "0.2.0"
|
||||||
|
|
|
||||||
|
|
@ -451,9 +451,23 @@ def pick_args(tty_input: TTYInput, tool: dict) -> Optional[dict]:
|
||||||
def main():
|
def main():
|
||||||
"""Entry point for cf command."""
|
"""Entry point for cf command."""
|
||||||
global _ui_out
|
global _ui_out
|
||||||
|
import argparse
|
||||||
import subprocess
|
import subprocess
|
||||||
import signal
|
import signal
|
||||||
|
|
||||||
|
# Parse metadata flags before touching stdin or /dev/tty. This keeps
|
||||||
|
# ``cf --help`` and ``cf --version`` usable in packaging checks, CI, and
|
||||||
|
# other non-interactive environments.
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="cf",
|
||||||
|
description="Interactively find and run local or registry CmdForge tools",
|
||||||
|
)
|
||||||
|
from .. import __version__
|
||||||
|
parser.add_argument(
|
||||||
|
"--version", action="version", version=f"%(prog)s {__version__}"
|
||||||
|
)
|
||||||
|
parser.parse_args()
|
||||||
|
|
||||||
# Handle Ctrl+C gracefully
|
# Handle Ctrl+C gracefully
|
||||||
def handle_sigint(sig, frame):
|
def handle_sigint(sig, frame):
|
||||||
# Restore cursor and exit cleanly
|
# Restore cursor and exit cleanly
|
||||||
|
|
|
||||||
|
|
@ -17,20 +17,21 @@ install together; optional extras add MCP, interactive PTY providers, and the re
|
||||||
<h2 id="pipx">A Clean Personal Installation</h2>
|
<h2 id="pipx">A Clean Personal Installation</h2>
|
||||||
<p><code>pipx</code> keeps CmdForge isolated while placing <code>cmdforge</code> and <code>cf</code> on
|
<p><code>pipx</code> keeps CmdForge isolated while placing <code>cmdforge</code> and <code>cf</code> on
|
||||||
your path:</p>
|
your path:</p>
|
||||||
<pre><code class="language-bash">pipx install 'cmdforge[all]'
|
<pre><code class="language-bash">pipx install 'cmdforge[mcp,pty]'
|
||||||
cmdforge --version
|
cmdforge --version
|
||||||
cmdforge --help</code></pre>
|
cmdforge --help</code></pre>
|
||||||
<p>A normal virtual environment works just as well:</p>
|
<p>A normal virtual environment works just as well:</p>
|
||||||
<pre><code class="language-bash">python3.10 -m venv .venv
|
<pre><code class="language-bash">python3.10 -m venv .venv
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
python -m pip install 'cmdforge[all]'</code></pre>
|
python -m pip install 'cmdforge[mcp,pty]'</code></pre>
|
||||||
|
|
||||||
<h2 id="extras">Choose Only the Extras You Need</h2>
|
<h2 id="extras">Choose Only the Extras You Need</h2>
|
||||||
<table class="w-full my-5"><thead class="bg-gray-100"><tr><th>Install</th><th>Adds</th></tr></thead><tbody>
|
<table class="w-full my-5"><thead class="bg-gray-100"><tr><th>Install</th><th>Adds</th></tr></thead><tbody>
|
||||||
<tr class="border-b"><td><code>cmdforge</code></td><td>CLI, desktop GUI, contracts, registry client, and local tool runner</td></tr>
|
<tr class="border-b"><td><code>cmdforge</code></td><td>CLI, desktop GUI, contracts, registry client, and local tool runner</td></tr>
|
||||||
<tr class="border-b"><td><code>cmdforge[mcp]</code></td><td>MCP client steps and MCP server integration</td></tr>
|
<tr class="border-b"><td><code>cmdforge[mcp]</code></td><td>MCP client steps and MCP server integration</td></tr>
|
||||||
<tr class="border-b"><td><code>cmdforge[pty]</code></td><td>Interactive pseudo-terminal providers</td></tr>
|
<tr class="border-b"><td><code>cmdforge[pty]</code></td><td>Interactive pseudo-terminal providers</td></tr>
|
||||||
<tr><td><code>cmdforge[all]</code></td><td>All optional runtime features, including the web stack</td></tr>
|
<tr><td><code>cmdforge[mcp,pty]</code></td><td>The recommended personal installation with agent integration</td></tr>
|
||||||
|
<tr><td><code>cmdforge[all]</code></td><td>Every optional feature, including the registry web-server stack</td></tr>
|
||||||
</tbody></table>
|
</tbody></table>
|
||||||
|
|
||||||
<h2 id="provider">Let CmdForge Inspect the Machine</h2>
|
<h2 id="provider">Let CmdForge Inspect the Machine</h2>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"""Tests for packaging metadata."""
|
"""Release metadata checks for the public Python distribution."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -9,12 +10,53 @@ if sys.version_info >= (3, 11):
|
||||||
else:
|
else:
|
||||||
tomllib = pytest.importorskip("tomli")
|
tomllib = pytest.importorskip("tomli")
|
||||||
|
|
||||||
|
from cmdforge import __version__
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
|
||||||
|
def _project_metadata() -> dict:
|
||||||
|
with (ROOT / "pyproject.toml").open("rb") as handle:
|
||||||
|
return tomllib.load(handle)["project"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_runtime_and_distribution_versions_match():
|
||||||
|
assert _project_metadata()["version"] == __version__
|
||||||
|
|
||||||
|
|
||||||
|
def test_public_distribution_metadata_is_complete():
|
||||||
|
project = _project_metadata()
|
||||||
|
|
||||||
|
assert project["name"] == "cmdforge"
|
||||||
|
assert project["readme"] == "PYPI_README.md"
|
||||||
|
assert project["license"] == "MIT"
|
||||||
|
assert project["license-files"] == ["LICENSE"]
|
||||||
|
assert project["requires-python"] == ">=3.10"
|
||||||
|
assert {"Homepage", "Documentation", "Repository", "Issues", "Changelog"} <= set(
|
||||||
|
project["urls"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_public_readme_describes_installation_and_current_capabilities():
|
||||||
|
readme = (ROOT / "PYPI_README.md").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
for term in (
|
||||||
|
'pipx install "cmdforge[mcp,pty]"',
|
||||||
|
"cmdforge providers discover",
|
||||||
|
"cmdforge mcp configure codex",
|
||||||
|
"--no-fallback",
|
||||||
|
"--result-envelope json",
|
||||||
|
):
|
||||||
|
assert term in readme
|
||||||
|
|
||||||
|
assert (ROOT / "LICENSE").is_file()
|
||||||
|
|
||||||
|
|
||||||
def test_web_templates_and_static_are_declared_as_package_data():
|
def test_web_templates_and_static_are_declared_as_package_data():
|
||||||
with open("pyproject.toml", "rb") as f:
|
with (ROOT / "pyproject.toml").open("rb") as handle:
|
||||||
data = tomllib.load(f)
|
data = tomllib.load(handle)
|
||||||
|
|
||||||
package_data = data["tool"]["setuptools"]["package-data"]["cmdforge.web"]
|
package_data = data["tool"]["setuptools"]["package-data"]["cmdforge.web"]
|
||||||
|
|
||||||
assert "templates/**/*.html" in package_data
|
assert "templates/**/*.html" in package_data
|
||||||
assert "static/**/*" in package_data
|
assert "static/**/*" in package_data
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
"""Behavioral coverage for registry-aware picker and deprecation UX."""
|
"""Behavioral coverage for registry-aware picker and deprecation UX."""
|
||||||
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import cmdforge.cli.picker as picker
|
import cmdforge.cli.picker as picker
|
||||||
|
import pytest
|
||||||
|
from cmdforge import __version__
|
||||||
from cmdforge.cli.picker import PickerResult
|
from cmdforge.cli.picker import PickerResult
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -89,3 +92,19 @@ def test_local_deprecation_selection_prints_migration_guidance():
|
||||||
output = picker._ui_out.getvalue()
|
output = picker._ui_out.getvalue()
|
||||||
assert "Moved." in output
|
assert "Moved." in output
|
||||||
assert "official/new" in output
|
assert "official/new" in output
|
||||||
|
|
||||||
|
|
||||||
|
def test_picker_help_does_not_require_a_terminal(capsys):
|
||||||
|
with patch.object(sys, "argv", ["cf", "--help"]), pytest.raises(SystemExit) as exc:
|
||||||
|
picker.main()
|
||||||
|
|
||||||
|
assert exc.value.code == 0
|
||||||
|
assert "Interactively find and run" in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
|
def test_picker_version_does_not_require_a_terminal(capsys):
|
||||||
|
with patch.object(sys, "argv", ["cf", "--version"]), pytest.raises(SystemExit) as exc:
|
||||||
|
picker.main()
|
||||||
|
|
||||||
|
assert exc.value.code == 0
|
||||||
|
assert f"cf {__version__}" in capsys.readouterr().out
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ def test_installation_matches_runtime_and_current_provider_onboarding():
|
||||||
|
|
||||||
assert "Python 3.10" in installation
|
assert "Python 3.10" in installation
|
||||||
assert "Python 3.8" not in installation
|
assert "Python 3.8" not in installation
|
||||||
assert "cmdforge[mcp]" in installation
|
assert "cmdforge[mcp,pty]" in installation
|
||||||
assert "cmdforge providers discover --add" in installation
|
assert "cmdforge providers discover --add" in installation
|
||||||
assert "cmdforge providers discover --add" in provider_setup
|
assert "cmdforge providers discover --add" in provider_setup
|
||||||
assert "--type api" in provider_setup
|
assert "--type api" in provider_setup
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue