"""CLI entry point for CmdForge.""" import argparse import sys from .. import __version__ from .tool_commands import ( cmd_list, cmd_create, cmd_edit, cmd_delete, cmd_test, cmd_run, cmd_ui, cmd_refresh, cmd_docs, cmd_check ) 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_remove, cmd_init, cmd_lock, cmd_verify from .config_commands import cmd_config from .settings_commands import cmd_settings from .system_deps_commands import cmd_system_deps def main(): """Main CLI entry point.""" parser = argparse.ArgumentParser( prog="cmdforge", description="A lightweight personal tool builder for AI-powered CLI commands" ) parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}") subparsers = parser.add_subparsers(dest="command", help="Available commands") # No command = launch UI # 'list' command p_list = subparsers.add_parser("list", help="List all tools") p_list.set_defaults(func=cmd_list) # 'create' command p_create = subparsers.add_parser("create", help="Create a new tool") p_create.add_argument("name", help="Tool name") p_create.add_argument("-d", "--description", help="Tool description") p_create.add_argument("-c", "--category", choices=["Text", "Developer", "Data", "Other"], default="Other", help="Tool category") p_create.add_argument("-p", "--prompt", help="Prompt template") p_create.add_argument("--provider", help="AI provider (default: mock)") p_create.add_argument("-f", "--force", action="store_true", help="Overwrite existing") p_create.set_defaults(func=cmd_create) # 'edit' command p_edit = subparsers.add_parser("edit", help="Edit a tool config") p_edit.add_argument("name", help="Tool name") p_edit.set_defaults(func=cmd_edit) # 'delete' command p_delete = subparsers.add_parser("delete", help="Delete a tool") p_delete.add_argument("name", help="Tool name") p_delete.add_argument("-f", "--force", action="store_true", help="Skip confirmation") p_delete.set_defaults(func=cmd_delete) # 'test' command p_test = subparsers.add_parser("test", help="Test a tool with mock provider") p_test.add_argument("name", help="Tool name") p_test.add_argument("-i", "--input", help="Input file for testing") p_test.add_argument("--dry-run", action="store_true", help="Show prompt only") p_test.set_defaults(func=cmd_test) # 'run' command p_run = subparsers.add_parser("run", help="Run a tool") p_run.add_argument("name", help="Tool name") p_run.add_argument("-i", "--input", help="Input file (reads from stdin if piped)") p_run.add_argument("-o", "--output", help="Output file (writes to stdout if omitted)") p_run.add_argument("--stdin", action="store_true", help="Read input interactively (type then Ctrl+D)") p_run.add_argument("-p", "--provider", help="Override provider") p_run.add_argument("--dry-run", action="store_true", help="Show what would happen without executing") p_run.add_argument("--show-prompt", action="store_true", help="Show prompts in addition to output") p_run.add_argument("-v", "--verbose", action="store_true", help="Show debug information") p_run.add_argument("tool_args", nargs=argparse.REMAINDER, help="Additional tool-specific arguments (use -- to separate)") p_run.set_defaults(func=cmd_run) # 'ui' command (explicit) p_ui = subparsers.add_parser("ui", help="Launch interactive UI") p_ui.set_defaults(func=cmd_ui) # 'refresh' command p_refresh = subparsers.add_parser("refresh", help="Refresh all wrapper scripts") p_refresh.set_defaults(func=cmd_refresh) # 'docs' command p_docs = subparsers.add_parser("docs", help="View or edit tool documentation") p_docs.add_argument("name", help="Tool name") p_docs.add_argument("-e", "--edit", action="store_true", help="Edit/create README in $EDITOR") p_docs.set_defaults(func=cmd_docs) # 'check' command p_check = subparsers.add_parser("check", help="Check dependencies for a tool (meta-tools)") p_check.add_argument("name", help="Tool name") p_check.set_defaults(func=cmd_check) # 'providers' command p_providers = subparsers.add_parser("providers", help="Manage AI providers") providers_sub = p_providers.add_subparsers(dest="providers_cmd", help="Provider commands") # providers list p_prov_list = providers_sub.add_parser("list", help="List all providers and their status") p_prov_list.set_defaults(func=cmd_providers) # providers check p_prov_check = providers_sub.add_parser("check", help="Check which providers are available") p_prov_check.set_defaults(func=cmd_providers) # providers install p_prov_install = providers_sub.add_parser("install", help="Interactive guide to install AI providers") p_prov_install.set_defaults(func=cmd_providers) # providers add p_prov_add = providers_sub.add_parser("add", help="Add or update a provider") p_prov_add.add_argument("name", help="Provider name") p_prov_add.add_argument("command", help="Command to run (e.g., 'claude -p')") p_prov_add.add_argument("-d", "--description", help="Provider description") p_prov_add.set_defaults(func=cmd_providers) # providers remove p_prov_remove = providers_sub.add_parser("remove", help="Remove a provider") p_prov_remove.add_argument("name", help="Provider name") p_prov_remove.set_defaults(func=cmd_providers) # providers test p_prov_test = providers_sub.add_parser("test", help="Test a provider") p_prov_test.add_argument("name", help="Provider name") p_prov_test.set_defaults(func=cmd_providers) # Default for providers with no subcommand p_providers.set_defaults(func=lambda args: cmd_providers(args) if args.providers_cmd else (setattr(args, 'providers_cmd', 'list') or cmd_providers(args))) # ------------------------------------------------------------------------- # Registry Commands # ------------------------------------------------------------------------- p_registry = subparsers.add_parser("registry", help="Registry commands (search, install, publish)") registry_sub = p_registry.add_subparsers(dest="registry_cmd", help="Registry commands") # registry search p_reg_search = registry_sub.add_parser("search", help="Search for tools") p_reg_search.add_argument("query", help="Search query") p_reg_search.add_argument("-c", "--category", help="Filter by category") p_reg_search.add_argument("-t", "--tag", action="append", dest="tags", help="Filter by tag (repeatable, AND logic)") p_reg_search.add_argument("-o", "--owner", help="Filter by publisher/owner") p_reg_search.add_argument("--min-downloads", type=int, help="Minimum downloads") p_reg_search.add_argument("--popular", action="store_true", help="Shortcut for --min-downloads 100") p_reg_search.add_argument("--new", action="store_true", help="Shortcut for --max-downloads 10") p_reg_search.add_argument("--since", help="Published after date (YYYY-MM-DD)") p_reg_search.add_argument("--before", help="Published before date (YYYY-MM-DD)") p_reg_search.add_argument("-s", "--sort", choices=["relevance", "downloads", "published_at", "name"], default="relevance", help="Sort by field") p_reg_search.add_argument("-l", "--limit", type=int, help="Max results (default: 20)") p_reg_search.add_argument("--json", action="store_true", help="Output as JSON") p_reg_search.add_argument("--show-facets", action="store_true", help="Show category/tag counts") p_reg_search.add_argument("--deprecated", action="store_true", help="Include deprecated tools") p_reg_search.set_defaults(func=cmd_registry) # registry tags p_reg_tags = registry_sub.add_parser("tags", help="List available tags") p_reg_tags.add_argument("-c", "--category", help="Filter tags by category") p_reg_tags.add_argument("-l", "--limit", type=int, default=50, help="Max tags to show (default: 50)") p_reg_tags.add_argument("--json", action="store_true", help="Output as JSON") p_reg_tags.set_defaults(func=cmd_registry) # registry install p_reg_install = registry_sub.add_parser("install", help="Install a tool from registry") p_reg_install.add_argument("tool", help="Tool to install (owner/name or name)") p_reg_install.add_argument("-v", "--version", help="Version constraint") p_reg_install.add_argument("-y", "--yes", action="store_true", help="Auto-install system packages without prompting") p_reg_install.add_argument("--no-system-deps", action="store_true", help="Skip system dependency check") p_reg_install.set_defaults(func=cmd_registry) # registry uninstall p_reg_uninstall = registry_sub.add_parser("uninstall", help="Uninstall a tool") p_reg_uninstall.add_argument("tool", help="Tool to uninstall (owner/name)") p_reg_uninstall.set_defaults(func=cmd_registry) # registry info p_reg_info = registry_sub.add_parser("info", help="Show tool information") p_reg_info.add_argument("tool", help="Tool name (owner/name)") p_reg_info.set_defaults(func=cmd_registry) # registry update p_reg_update = registry_sub.add_parser("update", help="Update local index cache") p_reg_update.set_defaults(func=cmd_registry) # registry publish p_reg_publish = registry_sub.add_parser("publish", help="Publish a tool to registry") p_reg_publish.add_argument("path", nargs="?", help="Path to tool directory (default: current dir)") p_reg_publish.add_argument("--dry-run", action="store_true", help="Validate without publishing") p_reg_publish.add_argument("-f", "--force", action="store_true", help="Skip confirmation prompts") p_reg_publish.set_defaults(func=cmd_registry) # registry my-tools p_reg_mytools = registry_sub.add_parser("my-tools", help="List your published tools") p_reg_mytools.set_defaults(func=cmd_registry) # registry status p_reg_status = registry_sub.add_parser("status", help="Check moderation status of a tool") p_reg_status.add_argument("tool", help="Tool name to check status for") p_reg_status.add_argument("--sync", action="store_true", help="Sync status from server and update local config") p_reg_status.set_defaults(func=cmd_registry) # registry browse p_reg_browse = registry_sub.add_parser("browse", help="Browse tools (TUI)") p_reg_browse.set_defaults(func=cmd_registry) # registry config (admin settings management) p_reg_config = registry_sub.add_parser("config", help="Manage registry settings (admin)") p_reg_config.add_argument("action", nargs="?", choices=["list", "get", "set"], default="list", help="Action to perform (default: list)") p_reg_config.add_argument("key", nargs="?", help="Setting key (for get/set)") p_reg_config.add_argument("value", nargs="?", help="Setting value (for set)") p_reg_config.add_argument("--json", action="store_true", help="Output as JSON") p_reg_config.add_argument("--category", "-c", help="Filter by category (for list)") p_reg_config.set_defaults(func=cmd_registry) # Default for registry with no subcommand p_registry.set_defaults(func=lambda args: cmd_registry(args) if args.registry_cmd else (setattr(args, 'registry_cmd', None) or cmd_registry(args))) # ------------------------------------------------------------------------- # Collections Commands # ------------------------------------------------------------------------- p_collections = subparsers.add_parser("collections", help="Manage tool collections") collections_sub = p_collections.add_subparsers(dest="collections_cmd", help="Collections commands") # collections list p_coll_list = collections_sub.add_parser("list", help="List available collections") p_coll_list.add_argument("--local", action="store_true", help="Show local collections only") p_coll_list.add_argument("--json", action="store_true", help="Output as JSON") p_coll_list.set_defaults(func=cmd_collections) # collections info p_coll_info = collections_sub.add_parser("info", help="Show registry collection details") p_coll_info.add_argument("name", help="Collection name") p_coll_info.add_argument("--json", action="store_true", help="Output as JSON") p_coll_info.set_defaults(func=cmd_collections) # collections install p_coll_install = collections_sub.add_parser("install", help="Install all tools in a registry collection") p_coll_install.add_argument("name", help="Collection name") p_coll_install.add_argument("--pinned", action="store_true", help="Use pinned versions from collection") p_coll_install.set_defaults(func=cmd_collections) # collections create (local) p_coll_create = collections_sub.add_parser("create", help="Create a new local collection") p_coll_create.add_argument("name", help="Collection name (slug)") p_coll_create.set_defaults(func=cmd_collections) # collections show (local) p_coll_show = collections_sub.add_parser("show", help="Show local collection details") p_coll_show.add_argument("name", help="Collection name") p_coll_show.set_defaults(func=cmd_collections) # collections add (local) p_coll_add = collections_sub.add_parser("add", help="Add tool to collection") p_coll_add.add_argument("collection", help="Collection name") p_coll_add.add_argument("tool", help="Tool reference (name or owner/name)") p_coll_add.add_argument("--version", "-v", help="Pin to specific version") p_coll_add.set_defaults(func=cmd_collections) # collections remove (local) p_coll_remove = collections_sub.add_parser("remove", help="Remove tool from collection") p_coll_remove.add_argument("collection", help="Collection name") p_coll_remove.add_argument("tool", help="Tool reference") p_coll_remove.set_defaults(func=cmd_collections) # collections delete (local) p_coll_delete = collections_sub.add_parser("delete", help="Delete local collection") p_coll_delete.add_argument("name", help="Collection name") p_coll_delete.add_argument("--force", "-f", action="store_true", help="Skip confirmation") p_coll_delete.set_defaults(func=cmd_collections) # collections publish p_coll_publish = collections_sub.add_parser("publish", help="Publish collection to registry") p_coll_publish.add_argument("name", help="Collection name") p_coll_publish.add_argument("--dry-run", action="store_true", help="Validate without publishing") p_coll_publish.add_argument("--continue", dest="resume", action="store_true", help="Resume pending publish after tools approved") p_coll_publish.set_defaults(func=cmd_collections) # collections status p_coll_status = collections_sub.add_parser("status", help="Check pending collection status") p_coll_status.add_argument("name", help="Collection name") p_coll_status.set_defaults(func=cmd_collections) # Default for collections with no subcommand p_collections.set_defaults(func=lambda args: cmd_collections(args) if args.collections_cmd else (setattr(args, 'collections_cmd', None) or cmd_collections(args))) # ------------------------------------------------------------------------- # Project Commands # ------------------------------------------------------------------------- # 'deps' command with subcommands p_deps = subparsers.add_parser("deps", help="Show project dependencies") deps_sub = p_deps.add_subparsers(dest="deps_cmd", help="Deps commands") # deps tree p_deps_tree = deps_sub.add_parser("tree", help="Show dependency tree") p_deps_tree.add_argument("--verbose", "-v", action="store_true", help="Show detailed info") p_deps_tree.set_defaults(func=cmd_deps_tree) # Default for deps with no subcommand shows basic deps list p_deps.set_defaults(func=cmd_deps) # 'install' command (for dependencies) p_install = subparsers.add_parser("install", help="Install dependencies from cmdforge.yaml") p_install.add_argument("--dry-run", action="store_true", help="Show what would be installed without installing") p_install.add_argument("--force", action="store_true", help="Install despite version conflicts or stale lock") p_install.add_argument("--verbose", "-v", action="store_true", help="Show detailed resolution info") p_install.add_argument("--ignore-lock", action="store_true", help="Ignore lock file and resolve fresh from manifest") p_install.add_argument("--frozen", action="store_true", help="Fail if lock file is missing (for CI)") p_install.add_argument("--strict-frozen", action="store_true", help="Fail if lock is missing or stale (stricter CI mode)") p_install.set_defaults(func=cmd_install_deps) # 'lock' command p_lock = subparsers.add_parser("lock", help="Generate or update cmdforge.lock") p_lock.add_argument("--force", "-f", action="store_true", help="Force regenerate even if up to date") p_lock.add_argument("--verbose", "-v", action="store_true", help="Show detailed output") p_lock.set_defaults(func=cmd_lock) # 'verify' command p_verify = subparsers.add_parser("verify", help="Verify installed tools match lock file") p_verify.set_defaults(func=cmd_verify) # 'add' command p_add = subparsers.add_parser("add", help="Add a tool to project dependencies") p_add.add_argument("tool", help="Tool to add (owner/name)") p_add.add_argument("-v", "--version", help="Version constraint (default: *)") 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") p_init.add_argument("-v", "--version", help="Project version") p_init.add_argument("-f", "--force", action="store_true", help="Overwrite existing") p_init.set_defaults(func=cmd_init) # ------------------------------------------------------------------------- # Config Commands # ------------------------------------------------------------------------- p_config = subparsers.add_parser("config", help="Manage configuration") config_sub = p_config.add_subparsers(dest="config_cmd", help="Config commands") # config show p_cfg_show = config_sub.add_parser("show", help="Show current configuration") p_cfg_show.set_defaults(func=cmd_config) # config set-token p_cfg_token = config_sub.add_parser("set-token", help="Set registry authentication token") p_cfg_token.add_argument("token", help="Registry token") p_cfg_token.set_defaults(func=cmd_config) # config set p_cfg_set = config_sub.add_parser("set", help="Set a configuration value") p_cfg_set.add_argument("key", help="Config key") p_cfg_set.add_argument("value", help="Config value") p_cfg_set.set_defaults(func=cmd_config) # config connect p_cfg_connect = config_sub.add_parser("connect", help="Connect this app to your CmdForge account") p_cfg_connect.add_argument("username", help="Your CmdForge username") p_cfg_connect.set_defaults(func=cmd_config) # config disconnect p_cfg_disconnect = config_sub.add_parser("disconnect", help="Disconnect from registry (clear token)") p_cfg_disconnect.set_defaults(func=cmd_config) # Default for config with no subcommand p_config.set_defaults(func=lambda args: cmd_config(args) if args.config_cmd else (setattr(args, 'config_cmd', 'show') or cmd_config(args))) # ------------------------------------------------------------------------- # Settings Commands # ------------------------------------------------------------------------- p_settings = subparsers.add_parser("settings", help="Manage tool settings") p_settings.add_argument("tool", help="Tool name") settings_sub = p_settings.add_subparsers(dest="settings_cmd") # settings show (default) p_settings_show = settings_sub.add_parser("show", help="Show current settings") p_settings_show.set_defaults(func=cmd_settings) # settings edit p_settings_edit = settings_sub.add_parser("edit", help="Edit settings in $EDITOR") p_settings_edit.set_defaults(func=cmd_settings) # settings reset p_settings_reset = settings_sub.add_parser("reset", help="Reset settings to defaults") p_settings_reset.add_argument("-f", "--force", action="store_true", help="Skip confirmation") p_settings_reset.set_defaults(func=cmd_settings) # settings diff p_settings_diff = settings_sub.add_parser("diff", help="Show differences from defaults") p_settings_diff.set_defaults(func=cmd_settings) # Default for settings with no subcommand p_settings.set_defaults(func=cmd_settings) # ------------------------------------------------------------------------- # System Dependencies Commands # ------------------------------------------------------------------------- p_sysdeps = subparsers.add_parser("system-deps", help="Manage system package dependencies") p_sysdeps.add_argument("tool", help="Tool name") sysdeps_sub = p_sysdeps.add_subparsers(dest="system_deps_cmd") # system-deps install p_sysdeps_install = sysdeps_sub.add_parser("install", help="Install missing system packages") p_sysdeps_install.add_argument("-y", "--yes", action="store_true", help="Install without prompting") p_sysdeps_install.set_defaults(func=cmd_system_deps) # Default for system-deps with no subcommand (show status) p_sysdeps.set_defaults(func=cmd_system_deps) args = parser.parse_args() # If no command, launch UI if args.command is None: return cmd_ui(args) return args.func(args) if __name__ == "__main__": sys.exit(main())