Add cmdforge remove command for project dependencies

- `cmdforge remove <tool>` removes a dependency from cmdforge.yaml
- Matches by exact name or short name (e.g., both "eli5" and "official/eli5" work)
- Returns exit code 1 if dependency not found
- Complements `cmdforge add` for full manifest management

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
rob 2026-01-26 23:45:54 -04:00
parent df29b28167
commit 93797a450a
3 changed files with 64 additions and 1 deletions

View File

@ -12,7 +12,7 @@ from .tool_commands import (
from .provider_commands import cmd_providers
from .registry_commands import cmd_registry
from .collections_commands import cmd_collections
from .project_commands import cmd_deps, cmd_deps_tree, cmd_install_deps, cmd_add, cmd_init, cmd_lock, cmd_verify
from .project_commands import cmd_deps, cmd_deps_tree, cmd_install_deps, cmd_add, cmd_remove, cmd_init, cmd_lock, cmd_verify
from .config_commands import cmd_config
@ -323,6 +323,11 @@ def main():
p_add.add_argument("--no-install", action="store_true", help="Don't install after adding")
p_add.set_defaults(func=cmd_add)
# 'remove' command
p_remove = subparsers.add_parser("remove", help="Remove a tool from project dependencies")
p_remove.add_argument("tool", help="Tool to remove (owner/name or just name)")
p_remove.set_defaults(func=cmd_remove)
# 'init' command
p_init = subparsers.add_parser("init", help="Initialize cmdforge.yaml")
p_init.add_argument("-n", "--name", help="Project name")

View File

@ -416,6 +416,39 @@ def cmd_add(args):
return 0
def cmd_remove(args):
"""Remove a tool from project dependencies."""
tool_spec = args.tool
# Find manifest
manifest_path = find_manifest()
if not manifest_path:
print("No cmdforge.yaml found in current project.")
print("Nothing to remove.")
return 1
manifest = load_manifest(manifest_path)
# Parse tool spec to get the name
parsed = ToolSpec.parse(tool_spec)
tool_name = parsed.full_name
# Remove from manifest
if manifest.remove_dependency(tool_name):
save_manifest(manifest, manifest_path)
print(f"Removed {tool_name} from {manifest_path.name}")
else:
# Try without owner prefix
if manifest.remove_dependency(parsed.name):
save_manifest(manifest, manifest_path)
print(f"Removed {parsed.name} from {manifest_path.name}")
else:
print(f"Dependency '{tool_name}' not found in {manifest_path.name}")
return 1
return 0
def cmd_init(args):
"""Initialize a new cmdforge.yaml."""
manifest_path = Path.cwd() / MANIFEST_FILENAME

View File

@ -141,6 +141,31 @@ class Manifest:
self.dependencies.append(Dependency(name=name, version=version))
def remove_dependency(self, name: str) -> bool:
"""Remove a dependency by name.
Args:
name: Tool name (can be qualified like 'official/tool' or just 'tool')
Returns:
True if dependency was removed, False if not found
"""
# Try exact match first
for i, dep in enumerate(self.dependencies):
if dep.name == name:
self.dependencies.pop(i)
return True
# Try matching just the tool name part (without owner)
short_name = name.split("/")[-1] if "/" in name else name
for i, dep in enumerate(self.dependencies):
dep_short = dep.name.split("/")[-1] if "/" in dep.name else dep.name
if dep_short == short_name:
self.dependencies.pop(i)
return True
return False
def find_manifest(start_dir: Optional[Path] = None) -> Optional[Path]:
"""