Skip to content

Commit 25c1a78

Browse files
Merge pull request #5 from Coding-Dev-Tools/Openclaw/devforge/fix-repo-urls
Openclaw/devforge/fix repo urls
2 parents 36e1b40 + 5f64051 commit 25c1a78

4 files changed

Lines changed: 93 additions & 27 deletions

File tree

README.md

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,28 @@
1-
# Revenue Holdings CLI
1+
# Revenue Holdings CLI
22

3-
[![GitHub stars](https://img.shields.io/github/stars/Coding-Dev-Tools/revenueholdings?style=social)](https://github.com/Coding-Dev-Tools/revenueholdings/stargazers)
3+
[![GitHub stars](https://img.shields.io/github/stars/Coding-Dev-Tools/devforge?style=social)](https://github.com/Coding-Dev-Tools/devforge/stargazers)
44

5-
**The `rh` command — one install, ten developer CLI tools.**
5+
**The `rh` command one install, ten developer CLI tools.**
66

77
[![PyPI](https://img.shields.io/pypi/v/revenueholdings)](https://pypi.org/project/revenueholdings/)
88
[![Python Versions](https://img.shields.io/pypi/pyversions/revenueholdings)](https://pypi.org/project/revenueholdings/)
99
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
1010

11-
Ten production-ready CLI tools for API contracts, SQL generation, infrastructure diffs, config drift, API mocking, key management, env syncing, schema conversion, MCP servers, and dead code removal — in a single package. Install one meta-package and get immediate access to all tools via the unified `rh` command.
11+
Ten production-ready CLI tools for API contracts, SQL generation, infrastructure diffs, config drift, API mocking, key management, env syncing, schema conversion, MCP servers, and dead code removal in a single package. Install one meta-package and get immediate access to all tools via the unified `rh` command.
1212

1313
---
1414

15-
[🏠 Landing Page](https://coding-dev-tools.github.io/revenueholdings.dev/) · [📝 Blog](https://coding-dev-tools.github.io/revenueholdings.dev/blog.html) · [🐛 Report a Bug](https://github.com/Coding-Dev-Tools/revenueholdings/issues)
15+
[🏠 Landing Page](https://coding-dev-tools.github.io/revenueholdings.dev/) · [📝 Blog](https://coding-dev-tools.github.io/revenueholdings.dev/blog.html) · [🐛 Report a Bug](https://github.com/Coding-Dev-Tools/devforge/issues)
1616

1717
---
1818

1919
## Why the Suite?
2020

2121
Instead of installing ten separate tools and learning ten different CLIs, `pip install revenueholdings[all]` gives you:
2222

23-
- **Single CLI** (`rh`) to invoke any tool — no context switching
23+
- **Single CLI** (`rh`) to invoke any tool no context switching
2424
- **Consistent flags, output formats, and help** across all tools
25-
- **Shared configuration** — one install, all tools
25+
- **Shared configuration** one install, all tools
2626

2727
## Installation
2828

@@ -55,34 +55,34 @@ rh versions # Show installed tool versions
5555
Run any tool directly through `rh`:
5656

5757
```bash
58-
# API Contract Guardian — detect OpenAPI breaking changes
58+
# API Contract Guardian detect OpenAPI breaking changes
5959
rh guard check spec-v1.yaml spec-v2.yaml
6060

61-
# json2sql — convert JSON to SQL INSERT statements
61+
# json2sql convert JSON to SQL INSERT statements
6262
rh sql convert data.json --dialect postgres
6363

64-
# DeployDiff — preview infrastructure changes with cost estimates
64+
# DeployDiff preview infrastructure changes with cost estimates
6565
rh deploy preview plan.json
6666

67-
# ConfigDrift — catch config drift between environments
67+
# ConfigDrift catch config drift between environments
6868
rh drift check dev.yaml prod.yaml
6969

70-
# APIGhost — spawn mock API server from OpenAPI spec
70+
# APIGhost spawn mock API server from OpenAPI spec
7171
rh ghost serve openapi.yaml
7272

73-
# APIAuth — generate API keys and JWTs
73+
# APIAuth generate API keys and JWTs
7474
rh auth generate --type api-key
7575

76-
# Envault — sync .env files across environments
76+
# Envault sync .env files across environments
7777
rh envault diff .env.dev .env.prod
7878

79-
# SchemaForge — convert between ORM formats
79+
# SchemaForge convert between ORM formats
8080
rh schema convert schema.prisma --to drizzle
8181

82-
# click-to-mcp — wrap CLI as MCP server
82+
# click-to-mcp wrap CLI as MCP server
8383
rh mcp wrap my-cli --transport http
8484

85-
# DeadCode — find unused exports in React/Next.js
85+
# DeadCode find unused exports in React/Next.js
8686
rh deadcode scan src/
8787
```
8888

@@ -97,20 +97,20 @@ rh deadcode scan src/
9797
| `ghost` | apighost | Mock API server from OpenAPI specs with VCR cassette recording and realistic fake data |
9898
| `auth` | apiauth | API key and JWT lifecycle management with AES-256-GCM encrypted local store |
9999
| `envault` | envault | Env variable syncing, diffing, and secret rotation with Vault/AWS SSM/Doppler/1Password support |
100-
| `schema` | schemaforge | Bidirectional ORM schema converter — 11 formats with zero-loss roundtripping |
101-
| `mcp` | click-to-mcp | Auto-wrap any Click/typer CLI as an MCP server — zero code changes |
100+
| `schema` | schemaforge | Bidirectional ORM schema converter 11 formats with zero-loss roundtripping |
101+
| `mcp` | click-to-mcp | Auto-wrap any Click/typer CLI as an MCP server zero code changes |
102102
| `deadcode` | deadcode | Detect unused exports, dead routes, orphaned CSS in TypeScript/React/Next.js projects |
103103

104104
## Links
105105

106106
- [Landing Page](https://coding-dev-tools.github.io/revenueholdings.dev/)
107107
- [GitHub Organization](https://github.com/Coding-Dev-Tools)
108-
- [Report an Issue](https://github.com/Coding-Dev-Tools/revenueholdings/issues)
108+
- [Report an Issue](https://github.com/Coding-Dev-Tools/devforge/issues)
109109

110110
## License
111111

112-
MIT — see [LICENSE](LICENSE) for details.
112+
MIT see [LICENSE](LICENSE) for details.
113113

114114
---
115115

116-
<sub>Built by [Revenue Holdings](https://coding-dev-tools.github.io/revenueholdings.dev/) — autonomous AI agents generating revenue 24/7.</sub>
116+
<sub>Built by [Revenue Holdings](https://coding-dev-tools.github.io/revenueholdings.dev/) autonomous AI agents generating revenue 24/7.</sub>

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ all = [
5252
dev = ["pytest>=7.0.0"]
5353

5454
[project.urls]
55-
Homepage = "https://github.com/Coding-Dev-Tools/revenueholdings"
56-
Repository = "https://github.com/Coding-Dev-Tools/revenueholdings"
57-
"Issue Tracker" = "https://github.com/Coding-Dev-Tools/revenueholdings/issues"
55+
Homepage = "https://github.com/Coding-Dev-Tools/devforge"
56+
Repository = "https://github.com/Coding-Dev-Tools/devforge"
57+
"Issue Tracker" = "https://github.com/Coding-Dev-Tools/devforge/issues"
5858

5959
[project.scripts]
6060
rh = "revenueholdings.cli:app"

src/revenueholdings/cli.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,11 @@ def show_versions(
120120
tool: str | None = typer.Argument(None, help="Check version of a specific tool."),
121121
):
122122
"""Show installed tool versions."""
123-
targets = ([tool] if tool in TOOLS else []) if tool else _builtins.list(TOOLS.keys())
123+
if tool is not None and tool not in TOOLS:
124+
console.print(f"[red]Unknown tool: {tool}[/red]")
125+
console.print(f"Available: {', '.join(TOOLS.keys())}")
126+
raise typer.Exit(code=1)
127+
targets = [tool] if tool else _builtins.list(TOOLS.keys())
124128

125129
for t in targets:
126130
info = TOOLS[t]

tests/test_cli.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from revenueholdings import TOOLS, __version__
55
from revenueholdings.cli import app
66
from typer.testing import CliRunner
7+
from unittest import mock
78

89
runner = CliRunner()
910

@@ -35,12 +36,73 @@ def test_unknown_tool(self):
3536
assert "Unknown" in result.stdout
3637

3738

39+
class TestInstallCommand:
40+
@mock.patch("revenueholdings.cli.subprocess.run")
41+
def test_install_specific_tool(self, mock_run):
42+
"""Install a specific tool by name."""
43+
mock_run.return_value = mock.MagicMock(returncode=0, stdout="", stderr="")
44+
result = runner.invoke(app, ["install", "guard"])
45+
assert result.exit_code == 0
46+
assert "Successfully" in result.stdout
47+
mock_run.assert_called_once()
48+
49+
@mock.patch("revenueholdings.cli.subprocess.run")
50+
def test_install_all(self, mock_run):
51+
"""Install all tools via the 'all' alias."""
52+
mock_run.return_value = mock.MagicMock(returncode=0, stdout="", stderr="")
53+
result = runner.invoke(app, ["install", "all"])
54+
assert result.exit_code == 0
55+
assert "Successfully" in result.stdout
56+
mock_run.assert_called_once()
57+
58+
def test_install_unknown_tool(self):
59+
"""Error on unknown tool name."""
60+
result = runner.invoke(app, ["install", "nonexistent"])
61+
assert result.exit_code == 1
62+
assert "Unknown" in result.stdout
63+
assert "Available:" in result.stdout
64+
65+
@mock.patch("revenueholdings.cli.subprocess.run")
66+
def test_install_failure(self, mock_run):
67+
"""Handle pip install failure gracefully."""
68+
mock_run.return_value = mock.MagicMock(returncode=1, stdout="", stderr="Error message")
69+
result = runner.invoke(app, ["install", "guard"])
70+
assert result.exit_code == 1
71+
assert "failed" in result.stdout.lower()
72+
73+
3874
class TestVersionsCommand:
3975
def test_versions_runs(self):
76+
"""List all tool versions without error."""
4077
result = runner.invoke(app, ["versions"])
41-
# Should succeed even if tools aren't installed
4278
assert result.exit_code == 0
4379

80+
def test_versions_unknown_tool_fails(self):
81+
"""Error on unknown tool name."""
82+
result = runner.invoke(app, ["versions", "nonexistent"])
83+
assert result.exit_code == 1
84+
assert "Unknown" in result.stdout
85+
86+
@mock.patch("revenueholdings.cli.subprocess.run")
87+
def test_versions_specific_tool_not_installed(self, mock_run):
88+
"""Show 'not installed' for a tool that isn't installed."""
89+
mock_run.return_value = mock.MagicMock(
90+
returncode=1, stdout="", stderr=""
91+
)
92+
result = runner.invoke(app, ["versions", "guard"])
93+
assert result.exit_code == 0
94+
assert "guard" in result.stdout
95+
assert "not installed" in result.stdout
96+
97+
98+
class TestDispatchCommands:
99+
def test_invalid_tool_subcommand(self):
100+
"""Reject dispatch to an unknown tool subcommand."""
101+
result = runner.invoke(app, ["nonexistent"])
102+
# typer outputs error to stderr, not stdout
103+
assert result.exit_code != 0
104+
assert "No such command" in result.stdout or "No such command" in result.stderr
105+
44106

45107
class TestHelp:
46108
def test_help(self):

0 commit comments

Comments
 (0)