[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/cecli-dev/cecli/main/cecli/args.py [Back]  [Original]

#!/usr/bin/env python

import argparse
import os
import sys
from pathlib import Path

import configargparse
import shtab

from cecli import __version__
from cecli.args_formatter import (
    DotEnvFormatter,
    MarkdownHelpFormatter,
    YamlHelpFormatter,
)
from cecli.deprecated_args import add_deprecated_mcp_args, add_deprecated_model_args

from .dump import dump  # noqa: F401

#  Deep-merge field sets 
# These define which config keys require special merge handling for
# .cecli.conf.yml files (deep-merge instead of shallow-overwrite).

DEEP_MERGE_LIST_FIELDS: frozenset[str] = frozenset(
    {
        #  Top-level argparse action="append" args 
        # These are excluded from configargparse for .cecli.conf.yml files
        # (to prevent shallow overwrite), so they must be deep-merged here.
        "rules",
        "file",
        "read",
        "mcp_servers_files",
        "acp_models",
        "set_env",
        "api_key",
        "alias",
        "exempt_paths",
        "lint_cmd",
        #  Nested agent-config array fields 
        "skills_paths",
        "skills_includelist",
        "skills_excludelist",
        "skills_init",
        "subagent_paths",
        "tools_paths",
        "tools_includelist",
        "tools_excludelist",
        "servers_includelist",
        "servers_excludelist",
        "allowed_commands",
    }
)


DEEP_MERGE_JSON_FIELDS: frozenset[str] = frozenset(
    {
        "agent_config",
        "mcp_servers",
        "hooks",
        "model_providers",
        "security_config",
        "retries",
        "custom",
        "system_one",
        "tui_config",
        "workflows",
    }
)


# Agent-config array fields (subset of DEEP_MERGE_LIST_FIELDS used by
# agent_coder to validate agent config before passing to sub-agents).
AGENT_CONFIG_LIST_FIELDS: frozenset[str] = frozenset(
    {
        "skills_paths",
        "skills_includelist",
        "skills_excludelist",
        "skills_init",
        "subagent_paths",
        "tools_paths",
        "tools_includelist",
        "tools_excludelist",
        "servers_includelist",
        "servers_excludelist",
        "allowed_commands",
    }
)


def resolve_cecli_ignore_path(path_str, git_root=None):
    path = Path(path_str)
    if path.is_absolute():
        return str(path)
    elif git_root:
        return str(Path(git_root) / path)
    return str(path)


def default_env_file(git_root):
    return os.path.join(git_root, ".env") if git_root else ".env"


def get_parser(default_config_files, git_root):
    parser = configargparse.ArgumentParser(
        description="cecli is AI pair programming in your terminal",
        add_config_file_help=True,
        default_config_files=default_config_files,
        config_file_parser_class=configargparse.YAMLConfigFileParser,
        auto_env_var_prefix="CECLI_",
    )
    # List of valid edit formats for argparse validation & shtab completion.
    # Dynamically gather them from the registered coder classes so the list
    # stays in sync if new formats are added.
    from cecli.coders import EDIT_FORMAT_MAP

    edit_format_choices = sorted(EDIT_FORMAT_MAP.keys())

    group = parser.add_argument_group("Main model")
    group.add_argument(
        "files", metavar="FILE", nargs="*", help="files to edit with an LLM (optional)"
    ).complete = shtab.FILE
    group.add_argument(
        "--model",
        metavar="MODEL",
        default=None,
        help="Specify the model to use for the main chat",
    )

    ##########
    group = parser.add_argument_group("API Keys and settings")
    group.add_argument(
        "--openai-api-key",
        help="Specify the OpenAI API key",
    )
    group.add_argument(
        "--anthropic-api-key",
        help="Specify the Anthropic API key",
    )
    group.add_argument(
        "--openai-api-base",
        help="Specify the api base url",
    )
    group.add_argument(
        "--set-env",
        action="append",
        metavar="ENV_VAR_NAME=value",
        help="Set an environment variable (to control API settings, can be used multiple times)",
        default=[],
    )
    group.add_argument(
        "--api-key",
        action="append",
        metavar="PROVIDER=KEY",
        help=(
            "Set an API key for a provider (eg: --api-key provider= sets"
            " PROVIDER_API_KEY=)"
        ),
        default=[],
    )
    group = parser.add_argument_group("Model settings")
    group.add_argument(
        "--list-models",
        "--models",
        metavar="MODEL",
        help="List known models which match the (partial) MODEL name",
    )
    group.add_argument(
        "--configure-provider",
        action="store_true",
        default=False,
        help="Force the interactive provider configuration wizard to run on startup",
    )
    group.add_argument(
        "--model-settings-file",
        metavar="MODEL_SETTINGS_FILE",
        default=".cecli.model.settings.yml",
        help="Specify a file with cecli model settings for unknown models",
    ).complete = shtab.FILE
    group.add_argument(
        "--model-metadata-file",
        metavar="MODEL_METADATA_FILE",
        default=".cecli.model.metadata.json",
        help="Specify a file with context window and costs for unknown models",
    ).complete = shtab.FILE
    group.add_argument(
        "--alias",
        action="append",
        metavar="ALIAS:MODEL",
        help="Add a model alias (can be used multiple times)",
    )
    group.add_argument(
        "--model-overrides",
        "--model-settings",
        metavar="MODEL_OVERRIDES_JSON",
        help=(
            'Specify model tag overrides directly as JSON/YAML string (e.g., \'{"gpt-4o": {"high":'
            ' {"temperature": 0.8}}}\')'
        ),
        default=None,
    )
    group.add_argument(
        "--model-overrides-file",
        metavar="MODEL_OVERRIDES_FILE",
        default=".cecli.model.overrides.yml",
        help=(
            "Specify a file with model tag overrides (e.g., gpt-4o:high -> reasoning_effort: high)"
        ),
    ).complete = shtab.FILE
    group.add_argument(
        "--model-providers",
        metavar="MODEL_PROVIDERS_JSON",
        help=(
            "Specify custom OpenAI-compatible model providers as a JSON/YAML string (e.g.,"
            ' \'{"my-provider": {"api_base": "https://...", "api_key_env": ["MY_KEY"]}}\')'
        ),
        default=None,
    )
    group.add_argument(
        "--reasoning-effort",
        type=str,
        help="Set the reasoning_effort API parameter (default: not set)",
    )
    group.add_argument(
        "--thinking-tokens",
        type=str,
        help=(
            "Set the thinking token budget for models that support it. Use 0 to disable. (default:"
            " not set)"
        ),
    )
    group.add_argument(
        "--show-thinking",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show reasoning content in the response (default: True)",
    )
    group.add_argument(
        "--verify-ssl",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Verify the SSL cert when connecting to models (default: True)",
    )
    group.add_argument(
        "--timeout",
        type=float,
        default=None,
        help="Timeout in seconds for API calls (default: None)",
    )
    group.add_argument(
        "--edit-format",
        "--chat-mode",
        metavar="EDIT_FORMAT",
        choices=edit_format_choices,
        default=None,
        help="Specify what edit format the LLM should use (default: agent)",
    )
    group.add_argument(
        "--ask",
        action="store_const",
        dest="edit_format",
        const="ask",
        help="Use ask edit format for the main chat",
    )
    group.add_argument(
        "--architect",
        action="store_const",
        dest="edit_format",
        const="architect",
        help="Use architect edit format for the main chat",
    )
    group.add_argument(
        "--agent",
        action="store_const",
        dest="edit_format",
        const="agent",
        help="Use agent edit format for the main chat (autonomous file management)",
    )
    group.add_argument(
        "--hashline",
        action="store_const",
        dest="edit_format",
        const="hashline",
        help="Use hashline edit format for the main chat",
    )
    group.add_argument(
        "--auto-accept-architect",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Enable/disable automatic acceptance of architect changes (default: True)",
    )
    group.add_argument(
        "--weak-model",
        metavar="WEAK_MODEL",
        default=None,
        help=(
            "Specify the model to use for commit messages and chat history summarization (default"
            " depends on --model)"
        ),
    )
    group.add_argument(
        "--editor-model",
        metavar="EDITOR_MODEL",
        default=None,
        help="Specify the model to use for editor tasks (default depends on --model)",
    )
    group.add_argument(
        "--editor-edit-format",
        metavar="EDITOR_EDIT_FORMAT",
        choices=edit_format_choices,
        default=None,
        help="Specify the edit format for the editor model (default: depends on editor model)",
    )
    group.add_argument(
        "--show-model-warnings",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Only work with models that have meta-data available (default: True)",
    )
    group.add_argument(
        "--check-model-accepts-settings",
        action=argparse.BooleanOptionalAction,
        default=True,
        help=(
            "Check if model accepts settings like reasoning_effort/thinking_tokens (default: True)"
        ),
    )
    group.add_argument(
        "--max-chat-history-tokens",
        type=int,
        default=None,
        help=(
            "Soft limit on tokens for chat history, after which summarization begins."
            " If unspecified, defaults to the model's max_chat_history_tokens."
        ),
    )
    group.add_argument(
        "--max-reflections",
        type=int,
        default=3,
        help="Maximum number of retries a model gets on malformed outputs (default: 3)",
    )
    group.add_argument(
        "--max-tool-calls",
        type=int,
        default=25,
        help="Maximum number of tool calls allowed per message (default: 25)",
    )
    group.add_argument(
        "--cost-limit",
        type=float,
        default=None,
        help="Cost limit per session, exceeding this forces prompt confirmation (default: None)",
    )
    group.add_argument(
        "--file-diffs",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Whether to store file diffs in context or reload files (default: True)",
    )
    group.add_argument(
        "--retries",
        metavar="RETRIES_JSON",
        help=(
            'Specify LLM retry configuration as a JSON/YAML string (e.g., \'{"retry_on_empty": '
            'true, "retry-on-unauthorized": false, "fallbacks": {"models": {"model": '
            '["backup-model"]}, "keys": {"openai": ["OPENAI_API_KEY", '
            '"OPENAI_API_KEY_2"]}}}\')'
        ),
        default=None,
    )

    #######
    group = parser.add_argument_group("Customization Settings")
    group.add_argument(
        "--custom",
        metavar="CUSTOM_JSON",
        help=(
            "Specify cecli customizations configurations (for prompts, commands, etc.) as a JSON"
            " string"
        ),
        default=None,
    )
    ########
    group = parser.add_argument_group("TUI Settings")

    env_val = os.environ.get("CECLI_TUI")

    if env_val is not None and env_val.lower() == "false":
        tui_default = False
        linear_output_default = True
    else:
        tui_default = None
        linear_output_default = None

    group.add_argument(
        "--tui",
        action=argparse.BooleanOptionalAction,
        default=tui_default,
        help="Launch Textual TUI interface",
    )
    group.add_argument(
        "--tui-config",
        metavar="TUI_CONFIG_JSON",
        help="Specify TUI Mode configuration as a JSON string",
        default=None,
    )
    #########
    group = parser.add_argument_group("Workspace Settings")
    group.add_argument(
        "--workspaces",
        type=str,
        help="JSON/YAML configuration for workspace initialization",
    )
    group.add_argument(
        "--workspace-name",
        type=str,
        help="Specify the workspace name to activate",
    )

    #########
    group = parser.add_argument_group("Agent Settings")
    group.add_argument(
        "--agent-config",
        metavar="AGENT_CONFIG_JSON",
        help="Specify Agent Mode configuration as a JSON string",
        default=None,
    )
    group.add_argument(
        "--hooks",
        metavar="HOOKS_CONFIG_JSON",
        help="Specify hooks configuration as a JSON string",
        default=None,
    )
    group.add_argument(
        "--workflows",
        metavar="WORKFLOWS_JSON",
        help=(
            "Specify workflows configuration as an inline JSON/YAML string (e.g.,"
            ' \'[{"name": "nightly", "tasks": [...]}]\') or a path to a workflows file'
        ),
        default=None,
    )
    group.add_argument(
        "--scheduler",
        metavar="SCHEDULER_CONFIG_JSON",
        help="Specify scheduler configuration as a JSON string",
        default=None,
    )
    group.add_argument(
        "--system-one",
        metavar="SYSTEM_ONE_JSON",
        help=(
            "Specify the System One decision endpoint as a JSON/YAML string (e.g.,"
            ' \'{"api_base": "http://127.0.0.1:8000", "api_key_env": ["SYSTEM_ONE_API_KEY"],'
            ' "model_name": "von-latest"}\')'
        ),
        default=None,
    )
    group.add_argument(
        "--agent-model",
        metavar="AGENT_MODEL",
        default=None,
        help="Specify the model to use for Agent mode (default depends on --model)",
    )
    group.add_argument(
        "--auto-save",
        action=argparse.BooleanOptionalAction,
        default=False,
        help=(
            "Enable/disable automatic saving of sessions as --auto-save-session-name (default:"
            " False)"
        ),
    )
    group.add_argument(
        "--auto-save-session-name",
        help="Specify session name for auto-save and auto-load (default: auto-save)",
        default="auto-save",
    )
    group.add_argument(
        "--auto-load",
        action=argparse.BooleanOptionalAction,
        default=False,
        help=(
            "Enable/disable automatic loading of --auto-save-session-name session on startup"
            " (default: False)"
        ),
    )
    group.add_argument(
        "--auto-memory",
        action=argparse.BooleanOptionalAction,
        default=False,
        help="Enable/disable automatic fact memorization after changes and compaction (default: True)",
    )
    group.add_argument(
        "--session-encrypt",
        action=argparse.BooleanOptionalAction,
        default=False,
        help=(
            "Encrypt saved sessions on disk (AES-256-GCM). Requires CECLI_SESSION_KEY or"
            " --session-key-file (default: False)"
        ),
    )
    group.add_argument(
        "--session-key-file",
        metavar="SESSION_KEY_FILE",
        default=None,
        help=(
            "File containing a urlsafe-base64 32-byte session encryption key"
            " (default: use CECLI_SESSION_KEY only)"
        ),
    ).complete = shtab.FILE
    group.add_argument(
        "--mcp-servers",
        metavar="MCP_CONFIG_JSON",
        help="Specify MCP server configurations as a JSON string",
        default=None,
    )
    group.add_argument(
        "--mcp-servers-files",
        metavar="MCP_CONFIG_FILES",
        help="Specify a file path with MCP server configurations (can be specified multiple times)",
        action="append",
        default=[],
    )
    group.add_argument(
        "--mcp-transport",
        metavar="MCP_TRANSPORT",
        help="Specify the transport for MCP servers (default: stdio)",
        default="stdio",
        choices=["stdio", "http", "sse"],
    )
    group.add_argument(
        "--preserve-todo-list",
        action="store_true",
        help=(
            "Deprecated: no longer needed because the todo list is saved and restored with"
            " sessions. This flag has no effect and will be removed."
        ),
        default=False,
    )
    group.add_argument(
        "--use-enhanced-map",
        action="store_true",
        help="Use enhanced Repo Map that takes into account imports (default: False)",
        default=True,
    )

    ##########
    group = parser.add_argument_group("Security Settings")
    group.add_argument(
        "--security-config",
        metavar="SECURITY_CONFIG_JSON",
        help=(
            'Specify Security configuration as a JSON string (e.g., \'{"allowed-domains":'
            ' ["github.com"]}\')'
        ),
        default=None,
    )

    ##########
    group = parser.add_argument_group("Context Compaction")
    group.add_argument(
        "--enable-context-compaction",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Enable automatic compaction of chat history to conserve tokens (default: False)",
    )
    group.add_argument(
        "--max-compaction-retries",
        type=int,
        default=3,
        metavar="N",
        choices=range(1, 11),
        help="Maximum number of compactions per request before propagating the error (default: 3, min: 1, max: 10)",
    )
    group.add_argument(
        "--context-compaction-max-tokens",
        type=float,
        default=None,
        help=(
            "The maximum number of tokens in the conversation before context compaction is"
            " triggered. (default: 80%% of model's context window)"
        ),
    )
    group.add_argument(
        "--context-compaction-summary-tokens",
        type=int,
        default=4096,
        help="The target maximum number of tokens for the generated summary. (default: 4096)",
    )
    group.add_argument(
        "--ignore-context-limit",
        action=argparse.BooleanOptionalAction,
        default=False,
        help=(
            "Ignore the model's context window limit when sending requests."
            " WARNING: This may cause API errors or truncated responses. (default: False)"
        ),
    )

    ##########
    group = parser.add_argument_group("Cache settings")
    group.add_argument(
        "--cache-prompts",
        action=argparse.BooleanOptionalAction,
        default=False,
        help="Enable caching of prompts (default: False)",
    )
    group.add_argument(
        "--cache-keepalive-pings",
        type=int,
        default=0,
        help="Number of times to ping at 5min intervals to keep prompt cache warm (default: 0)",
    )

    ##########
    group = parser.add_argument_group("Rate limiting")
    group.add_argument(
        "--tokens-per-minute",
        type=int,
        default=1000000,
        help=(
            "Set the maximum tokens sent per minute before rate limiting sleeps are"
            " inserted before LLM API calls (default: 1000000, use 0 to disable)"
        ),
    )

    ##########
    group = parser.add_argument_group("Repomap settings")
    group.add_argument(
        "--map-tokens",
        type=int,
        default=None,
        help="Suggested number of tokens to use for repo map, use 0 to disable",
    )
    group.add_argument(
        "--map-refresh",
        choices=["auto", "always", "files", "manual"],
        default="auto",
        help=(
            "Control how often the repo map is refreshed. Options: auto, always, files, manual"
            " (default: auto)"
        ),
    )
    group.add_argument(
        "--map-multiplier-no-files",
        type=float,
        default=2,
        help="Multiplier for map tokens when no files are specified (default: 2)",
    )
    group.add_argument(
        "--map-max-line-length",
        type=int,
        default=100,
        help=(
            "Maximum line length for the repo map code. Prevents sending crazy long lines of"
            " minified JS files etc. (default: 100)"
        ),
    )
    group.add_argument(
        "--map-cache-dir",
        metavar="MAP_CACHE_DIR",
        dest="map_cache_dir",
        default=".",
        help=(
            "Directory for the repository map cache .cecli.tags.cache.v#"
            " (default: current directory)"
        ),
    )
    group.add_argument(
        "--map-memory-cache",
        action="store_true",
        help="Store repo map in memory (default: False)",
        default=False,
    )
    ##########
    group = parser.add_argument_group("History Files")
    default_input_history_file = (
        os.path.join(git_root, ".cecli/input.history") if git_root else ".cecli/input.history"
    )
    default_chat_history_file = (
        os.path.join(git_root, ".cecli/chat.history") if git_root else ".cecli/chat.history"
    )
    group.add_argument(
        "--input-history-file",
        metavar="INPUT_HISTORY_FILE",
        default=default_input_history_file,
        help=f"Specify the chat input history file (default: {default_input_history_file})",
    ).complete = shtab.FILE
    group.add_argument(
        "--chat-history-file",
        metavar="CHAT_HISTORY_FILE",
        default=default_chat_history_file,
        help=f"Specify the chat history file (default: {default_chat_history_file})",
    ).complete = shtab.FILE
    #########
    group = parser.add_argument_group("Input settings")
    group.add_argument(
        "--terminal-setup",
        action=argparse.BooleanOptionalAction,
        default=False,
        help=(
            "Auto-configure terminal emulator for shift+enter support for new lines (default:"
            " False)"
        ),
    )
    group.add_argument(
        "--exempt-paths",
        action="append",
        default=[],
        help=(
            "Specify a regex pattern for paths that should be exempted from file creation. "
            "When /add matches a path matching any exempt pattern, it will not offer to "
            "create the file. Can be used multiple times."
        ),
    )
    ##########
    group = parser.add_argument_group("Output settings")
    group.add_argument(
        "--dark-mode",
        action="store_true",
        help="Use colors suitable for a dark terminal background (default: False)",
        default=False,
    )
    group.add_argument(
        "--light-mode",
        action="store_true",
        help="Use colors suitable for a light terminal background (default: False)",
        default=False,
    )
    group.add_argument(
        "--pretty",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Enable/disable pretty, colorized output (default: True)",
    )
    group.add_argument(
        "--stream",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Enable/disable streaming responses (default: True)",
    )
    group.add_argument(
        "--spinner",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Enable/disable the spinner while waiting for LLM responses (default: True)",
    )
    group.add_argument(
        "--user-input-color",
        default="#00cc00",
        help="Set the color for user input (default: #00cc00)",
    )
    group.add_argument(
        "--tool-output-color",
        default=None,
        help="Set the color for tool output (default: None)",
    )
    group.add_argument(
        "--tool-error-color",
        default="#FF2222",
        help="Set the color for tool error messages (default: #FF2222)",
    )
    group.add_argument(
        "--tool-warning-color",
        default="#FFA500",
        help="Set the color for tool warning messages (default: #FFA500)",
    )
    group.add_argument(
        "--assistant-output-color",
        default="#0088ff",
        help="Set the color for assistant output (default: #0088ff)",
    )
    group.add_argument(
        "--show-speed",
        action="store_true",
        help="Show token processing and generation speed in usage report (default: False)",
        default=False,
    )
    group.add_argument(
        "--use-reminders",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Enable/disable injecting reminder messages (default: True)",
    )
    group.add_argument(
        "--completion-menu-color",
        metavar="COLOR",
        default=None,
        help="Set the color for the completion menu (default: terminal's default text color)",
    )
    group.add_argument(
        "--completion-menu-bg-color",
        metavar="COLOR",
        default=None,
        help=(
            "Set the background color for the completion menu (default: terminal's default"
            " background color)"
        ),
    )
    group.add_argument(
        "--completion-menu-current-color",
        metavar="COLOR",
        default=None,
        help=(
            "Set the color for the current item in the completion menu (default: terminal's default"
            " background color)"
        ),
    )
    group.add_argument(
        "--completion-menu-current-bg-color",
        metavar="COLOR",
        default=None,
        help=(
            "Set the background color for the current item in the completion menu (default:"
            " terminal's default text color)"
        ),
    )
    group.add_argument(
        "--code-theme",
        default="default",
        help=(
            "Set the markdown code theme (default: default, other options include monokai,"
            " solarized-dark, solarized-light, or a Pygments builtin style,"
            " see https://pygments.org/styles for available themes)"
        ),
    )
    group.add_argument(
        "--show-diffs",
        action="store_true",
        help="Show diffs when committing changes (default: False)",
        default=False,
    )

    ##########
    group = parser.add_argument_group("Git settings")
    group.add_argument(
        "--git",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Enable/disable looking for a git repo (default: True)",
    )
    group.add_argument(
        "--gitignore",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Enable/disable adding .cecli* to .gitignore (default: True)",
    )
    group.add_argument(
        "--add-gitignore-files",
        action=argparse.BooleanOptionalAction,
        default=False,
        help="Enable/disable the addition of files listed in .gitignore to cecli's editing scope.",
    )
    default_cecli_ignore_file = (
        os.path.join(git_root, ".cecli.ignore") if git_root else ".cecli.ignore"
    )

    group.add_argument(
        "--cecli-ignore",
        metavar="CECLI_IGNORE",
        type=lambda path_str: resolve_cecli_ignore_path(path_str, git_root),
        default=default_cecli_ignore_file,
        help="Specify the cecli ignore file (default: .cecli.ignore in git root)",
    ).complete = shtab.FILE
    group.add_argument(
        "--subtree-only",
        action="store_true",
        help="Only consider files in the current subtree of the git repository",
        default=False,
    )
    group.add_argument(
        "--auto-commits",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Enable/disable auto commit of LLM changes (default: True)",
    )
    group.add_argument(
        "--dirty-commits",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Enable/disable commits when repo is found dirty (default: True)",
    )
    group.add_argument(
        "--attribute-author",
        action=argparse.BooleanOptionalAction,
        default=None,
        help=(
            "Attribute cecli code changes in the git author name (default: True). If explicitly set"
            " to True, overrides --attribute-co-authored-by precedence."
        ),
    )
    group.add_argument(
        "--attribute-committer",
        action=argparse.BooleanOptionalAction,
        default=None,
        help=(
            "Attribute cecli commits in the git committer name (default: True). If explicitly set"
            " to True, overrides --attribute-co-authored-by precedence for cecli edits."
        ),
    )
    group.add_argument(
        "--attribute-commit-message-author",
        action=argparse.BooleanOptionalAction,
        default=False,
        help="Prefix commit messages with 'cecli: ' if cecli authored the changes (default: False)",
    )
    group.add_argument(
        "--attribute-commit-message-committer",
        action=argparse.BooleanOptionalAction,
        default=False,
        help="Prefix all commit messages with 'cecli: ' (default: False)",
    )
    group.add_argument(
        "--attribute-co-authored-by",
        action=argparse.BooleanOptionalAction,
        default=True,
        help=(
            "Attribute cecli edits using the Co-authored-by trailer in the commit message"
            " (default: True). If True, this takes precedence over default --attribute-author and"
            " --attribute-committer behavior unless they are explicitly set to True."
        ),
    )
    group.add_argument(
        "--git-commit-verify",
        action=argparse.BooleanOptionalAction,
        default=False,
        help="Enable/disable git pre-commit hooks with --no-verify (default: False)",
    )
    group.add_argument(
        "--commit",
        action="store_true",
        help="Commit all pending changes with a suitable commit message, then exit",
        default=False,
    )
    group.add_argument(
        "--commit-prompt",
        metavar="PROMPT",
        help="Specify a custom prompt for generating commit messages",
    )
    group.add_argument(
        "--dry-run",
        action=argparse.BooleanOptionalAction,
        default=False,
        help="Perform a dry run without modifying files (default: False)",
    )
    group.add_argument(
        "--skip-sanity-check-repo",
        action="store_true",
        help="Skip the sanity check for the git repository (default: False)",
        default=False,
    )
    group.add_argument(
        "--watch-files",
        action=argparse.BooleanOptionalAction,
        default=False,
        help="Enable/disable watching files for ai coding comments (default: False)",
    )
    group = parser.add_argument_group("Fixing and committing")
    group.add_argument(
        "--lint",
        action="store_true",
        help="Lint and fix provided files, or dirty files if none provided",
        default=False,
    )
    group.add_argument(
        "--lint-cmd",
        action="append",
        help=(
            'Specify lint commands to run for different languages, eg: "python: flake8'
            ' --select=..." (can be used multiple times)'
        ),
        default=[],
    )
    group.add_argument(
        "--auto-lint",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Enable/disable automatic linting after changes (default: True)",
    )
    group.add_argument(
        "--test-cmd",
        help="Specify command to run tests",
        default=[],
    )
    group.add_argument(
        "--auto-test",
        action=argparse.BooleanOptionalAction,
        default=False,
        help="Enable/disable automatic testing after changes (default: False)",
    )
    group.add_argument(
        "--test",
        action="store_true",
        help="Run tests, fix problems found and then exit",
        default=False,
    )

    #########
    group = parser.add_argument_group("Upgrading")
    group.add_argument(
        "--just-check-update",
        action="store_true",
        help="Check for updates and return status in the exit code",
        default=False,
    )
    group.add_argument(
        "--check-update",
        action=argparse.BooleanOptionalAction,
        help="Check for new cecli versions on launch",
        default=True,
    )
    group.add_argument(
        "--show-release-notes",
        action=argparse.BooleanOptionalAction,
        help="Show release notes on first run of new version (default: None, ask user)",
        default=None,
    )
    group.add_argument(
        "--upgrade",
        "--update",
        action="store_true",
        help="Upgrade cecli to the latest version from PyPI",
        default=False,
    )
    group.add_argument(
        "--version",
        action="version",
        version=f"%(prog)s {__version__}",
        help="Show the version number and exit",
    )

    ##########
    group = parser.add_argument_group("Modes")
    group.add_argument(
        "--message",
        "--msg",
        "-m",
        metavar="COMMAND",
        help=(
            "Specify a single message to send the LLM, process reply then exit (disables chat mode)"
        ),
    )
    group.add_argument(
        "--message-file",
        "-f",
        metavar="MESSAGE_FILE",
        help=(
            "Specify a file containing the message to send the LLM, process reply, then exit"
            " (disables chat mode)"
        ),
    ).complete = shtab.FILE
    group.add_argument(
        "--copy-paste",
        action=argparse.BooleanOptionalAction,
        default=False,
        help="Enable automatic copy/paste of chat between cecli and web UI (default: False)",
    )
    group.add_argument(
        "--apply",
        metavar="FILE",
        help="Apply the changes from the given file instead of running the chat (debug)",
    ).complete = shtab.FILE
    group.add_argument(
        "--apply-clipboard-edits",
        action="store_true",
        help="Apply clipboard contents as edits using the main model's editor format",
        default=False,
    )
    group.add_argument(
        "--exit",
        action="store_true",
        help="Do all startup activities then exit before accepting user input (debug)",
        default=False,
    )
    group.add_argument(
        "--show-repo-map",
        action="store_true",
        help="Print the repo map and exit (debug)",
        default=False,
    )
    group.add_argument(
        "--show-prompts",
        action="store_true",
        help="Print the system prompts and exit (debug)",
        default=False,
    )
    group.add_argument(
        "--linear-output",
        action=argparse.BooleanOptionalAction,
        help="Run input and output sequentially instead of us simultaneous streams (default: None)",
        default=linear_output_default,
    )
    group.add_argument(
        "--debug",
        action="store_true",
        help="Turn on verbose debugging (default: False)",
        default=False,
    )
    ##########
    group = parser.add_argument_group("Voice settings")
    group.add_argument(
        "--voice-format",
        metavar="VOICE_FORMAT",
        default="wav",
        choices=["wav", "mp3", "webm"],
        help="Audio format for voice recording (default: wav). webm and mp3 require ffmpeg",
    )
    group.add_argument(
        "--voice-language",
        metavar="VOICE_LANGUAGE",
        default=None,
        help=(
            "Specify the language for voice using ISO 639-1 code "
            "(default: resolve from the voice setting, then chat language)"
        ),
    )
    group.add_argument(
        "--voice-input-device",
        metavar="VOICE_INPUT_DEVICE",
        default=None,
        help="Specify the input device name for voice recording",
    )

    ######
    group = parser.add_argument_group("Other settings")
    group.add_argument(
        "--tweak-responses",
        action="store_true",
        help="Allow manual edits to model responses (default: False)",
        default=False,
    )
    group.add_argument(
        "--yes-always",
        "--yes",
        action="store_true",
        help="Always say yes to every confirmation (not including cli commands)",
        default=None,
    )
    group.add_argument(
        "--yes-always-commands",
        "--yolo",
        action="store_true",
        help="Always say yes to every confirmation (including cli commands)",
        default=False,
    )
    group.add_argument(
        "--disable-playwright",
        action="store_true",
        help="Never prompt for or attempt to install Playwright for web scraping (default: False).",
        default=False,
    )
    group.add_argument(
        "--disable-scraping",
        action="store_true",
        help="Disable automatic url scraping entirely web scraping (default: False).",
        default=False,
    )
    group.add_argument(
        "--file",
        action="append",
        metavar="FILE",
        help="specify a file to edit (can be used multiple times, glob patterns supported)",
    ).complete = shtab.FILE
    group.add_argument(
        "--read",
        action="append",
        metavar="FILE",
        help="specify a read-only file (can be used multiple times, glob patterns supported)",
    ).complete = shtab.FILE
    group.add_argument(
        "--rules",
        action="append",
        metavar="FILE",
        help="specify a rules file (can be used multiple times, glob patterns supported)",
    ).complete = shtab.FILE
    group.add_argument(
        "--vim",
        action="store_true",
        help="Use VI editing mode in the terminal (default: False)",
        default=False,
    )
    group.add_argument(
        "--chat-language",
        metavar="CHAT_LANGUAGE",
        default=None,
        help="Specify the language to use in the chat (default: None, uses system settings)",
    )
    group.add_argument(
        "--commit-language",
        metavar="COMMIT_LANGUAGE",
        default=None,
        help="Specify the language to use in the commit message (default: None, user language)",
    )
    group.add_argument(
        "-v",
        "--verbose",
        action="store_true",
        help="Enable verbose output",
        default=False,
    )
    group.add_argument(
        "--load",
        metavar="LOAD_FILE",
        help="Load and execute /commands from a file on launch",
    ).complete = shtab.FILE
    group.add_argument(
        "--encoding",
        default="utf-8",
        help="Specify the encoding for input and output (default: utf-8)",
    )
    group.add_argument(
        "--line-endings",
        choices=["platform", "lf", "crlf"],
        default="platform",
        help="Line endings to use when writing files (default: platform)",
    )
    group.add_argument(
        "-c",
        "--config",
        is_config_file=True,
        env_var="CECLI_CONFIG_FILE",
        metavar="CONFIG_FILE",
        help=(
            "Specify the config file (default: search for .cecli.conf.yml in git root, cwd"
            " or home directory)"
        ),
    ).complete = shtab.FILE
    # This is a duplicate of the argument in the preparser and is a no-op by this time of
    # argument parsing, but it's here so that the help is displayed as expected.
    group.add_argument(
        "--env-file",
        metavar="ENV_FILE",
        default=default_env_file(git_root),
        help="Specify the .env file to load (default: .env in git root)",
    ).complete = shtab.FILE
    group.add_argument(
        "--suggest-shell-commands",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Enable/disable suggesting shell commands (default: True)",
    )
    group.add_argument(
        "--fancy-input",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Enable/disable fancy input with history and completion (default: True)",
    )
    group.add_argument(
        "--multiline",
        action=argparse.BooleanOptionalAction,
        default=False,
        help="Enable/disable multi-line input mode with Meta-Enter to submit (default: False)",
    )
    group.add_argument(
        "--notifications",
        action=argparse.BooleanOptionalAction,
        default=False,
        help=(
            "Enable/disable terminal bell notifications when LLM responses are ready (default:"
            " False)"
        ),
    )
    group.add_argument(
        "--notifications-command",
        metavar="COMMAND",
        default=None,
        help=(
            "Specify a command to run for notifications instead of the terminal bell. If not"
            " specified, a default command for your OS may be used."
        ),
    )
    group.add_argument(
        "--notification-bell",
        action=argparse.BooleanOptionalAction,
        default=False,
        help=(
            "Allow notification commands to produce an audible bell. When enabled, command"
            " output is not suppressed so terminal bell escape sequences can ring through"
            " (default: True)"
        ),
    )
    group.add_argument(
        "--command-prefix",
        default=None,
        help="Specify a command prefix for all commands (useful for sandboxing)",
    )
    group.add_argument(
        "--detect-urls",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Enable/disable detection and offering to add URLs to chat (default: True)",
    )
    group.add_argument(
        "--editor",
        help="Specify which editor to use for the /editor command",
    )

    supported_shells_list = sorted(list(shtab.SUPPORTED_SHELLS))
    group.add_argument(
        "--shell-completions",
        metavar="SHELL",
        choices=supported_shells_list,
        help=(
            "Print shell completion script for the specified SHELL and exit. Supported shells:"
            f" {', '.join(supported_shells_list)}. Example: cecli --shell-completions bash"
        ),
    )

    ##########
    group = parser.add_argument_group("Server settings")
    group.add_argument(
        "--server-config",
        metavar="SERVER_CONFIG_JSON",
        help="Specify server configuration (WebSocket and headless) as a JSON/YAML string",
        default=None,
    )
    group.add_argument(
        "--acp",
        action="store_true",
        default=False,
        help=(
            "Run as an Agent Client Protocol (ACP) agent over stdio, for editors such as Zed"
            " (stdout carries the protocol)"
        ),
    )
    group.add_argument(
        "--acp-models",
        action="append",
        metavar="PATTERN",
        help=(
            "Limit the ACP model picker's provider lists to models matching this pattern,"
            " matched like /models (can be used multiple times)"
        ),
    )

    ##########
    group = parser.add_argument_group("Deprecated model settings")
    # Add deprecated model shortcut arguments
    add_deprecated_model_args(parser, group)

    group = parser.add_argument_group("Deprecated agent settings")
    add_deprecated_mcp_args(parser, group)

    return parser


def get_md_help():
    os.environ["COLUMNS"] = "70"
    sys.argv = ["cecli"]
    parser = get_parser([], None)

    # This instantiates all the action.env_var values
    parser.parse_known_args()

    parser.formatter_class = MarkdownHelpFormatter

    return argparse.ArgumentParser.format_help(parser)


def get_sample_yaml():
    os.environ["COLUMNS"] = "100"
    sys.argv = ["cecli"]
    parser = get_parser([], None)

    # This instantiates all the action.env_var values
    parser.parse_known_args()

    parser.formatter_class = YamlHelpFormatter

    return argparse.ArgumentParser.format_help(parser)


def get_sample_dotenv():
    os.environ["COLUMNS"] = "120"
    sys.argv = ["cecli"]
    parser = get_parser([], None)

    # This instantiates all the action.env_var values
    parser.parse_known_args()

    parser.formatter_class = DotEnvFormatter

    return argparse.ArgumentParser.format_help(parser)


def main():
    if len(sys.argv) > 1:
        command = sys.argv[1]
    else:
        command = "yaml"  # Default to yaml if no command is given

    if command == "md":
        print(get_md_help())
    elif command == "dotenv":
        print(get_sample_dotenv())
    elif command == "yaml":
        print(get_sample_yaml())
    elif command == "completion":
        if len(sys.argv) > 2:
            shell = sys.argv[2]
            if shell not in shtab.SUPPORTED_SHELLS:
                print(f"Error: Unsupported shell '{shell}'.", file=sys.stderr)
                print(
                    f"Supported shells are: {', '.join(shtab.SUPPORTED_SHELLS)}",
                    file=sys.stderr,
                )
                sys.exit(1)
            parser = get_parser([], None)
            parser.prog = "cecli"  # Set the program name on the parser
            print(shtab.complete(parser, shell=shell))
        else:
            print("Error: Please specify a shell for completion.", file=sys.stderr)
            print(f"Usage: python {sys.argv[0]} completion ", file=sys.stderr)
            print(
                f"Supported shells are: {', '.join(shtab.SUPPORTED_SHELLS)}",
                file=sys.stderr,
            )
            sys.exit(1)
    else:
        # Default to YAML for any other unrecognized argument, or if 'yaml' was explicitly passed
        print(get_sample_yaml())


if __name__ == "__main__":
    status = main()
    sys.exit(status)

Web Proxy Viewer  |  New URL  |  Original Page