From f9f1c92c0faedcb45ce9c9cb3d8330592a16ac31 Mon Sep 17 00:00:00 2001 From: zanjonke Date: Tue, 13 Jan 2026 12:52:31 +0100 Subject: [PATCH 1/6] Adding the packaging feature --- .gitignore | 3 + README.md | 4 +- config/__init__.py | 2 + .../system_config.yaml | 0 plain2code.py | 67 +++++----- plain2code_arguments.py | 7 ++ pyproject.toml | 114 +++++++++++++++++- standard_template_library/__init__.py | 1 + system_config.py | 6 +- 9 files changed, 167 insertions(+), 37 deletions(-) create mode 100644 config/__init__.py rename system_config.yaml => config/system_config.yaml (100%) create mode 100644 standard_template_library/__init__.py diff --git a/.gitignore b/.gitignore index 07a72f2..a4af275 100644 --- a/.gitignore +++ b/.gitignore @@ -23,5 +23,8 @@ examples/**/node_plain_modules/ *.log .venv +build +dist +*.egg-info .env \ No newline at end of file diff --git a/README.md b/README.md index 30c96de..46fad81 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Codeplain is a platform that generates software code using large language models Schematic overview of the Codeplain's code generation service - + ### Abstracting Away Code Generation Complexity with ***plain @@ -17,7 +17,7 @@ Schematic overview of the Codeplain's code generation service An example application in ***plain - + ## Getting started diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..d8f6573 --- /dev/null +++ b/config/__init__.py @@ -0,0 +1,2 @@ +# Configuration files for plain2code + diff --git a/system_config.yaml b/config/system_config.yaml similarity index 100% rename from system_config.yaml rename to config/system_config.yaml diff --git a/plain2code.py b/plain2code.py index 1cae9a5..677e2d3 100644 --- a/plain2code.py +++ b/plain2code.py @@ -1,4 +1,4 @@ -import importlib.util +import importlib.resources import logging import logging.config import os @@ -9,6 +9,7 @@ from liquid2.exceptions import TemplateNotFoundError from requests.exceptions import RequestException +import codeplain_REST_api as codeplain_api import file_utils import plain_file import plain_spec @@ -31,9 +32,8 @@ from tui.plain2code_tui import Plain2CodeTUI TEST_SCRIPT_EXECUTION_TIMEOUT = 120 # 120 seconds -LOGGING_CONFIG_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logging_config.yaml") -DEFAULT_TEMPLATE_DIRS = "standard_template_library" +DEFAULT_TEMPLATE_DIRS = importlib.resources.files("standard_template_library") MAX_UNITTEST_FIX_ATTEMPTS = 20 MAX_CONFORMANCE_TEST_FIX_ATTEMPTS = 20 @@ -109,16 +109,15 @@ def setup_logging( log_file_path = get_log_file_path(plain_file_path, log_file_name) - # Try to load logging configuration from YAML file - if os.path.exists(LOGGING_CONFIG_PATH): - try: - with open(LOGGING_CONFIG_PATH, "r") as f: - config = yaml.safe_load(f) - logging.config.dictConfig(config) - console.info(f"Loaded logging configuration from {LOGGING_CONFIG_PATH}") - except Exception as e: - logging.basicConfig() - console.warning(f"Failed to load logging configuration from {LOGGING_CONFIG_PATH}: {str(e)}") + # Try to load logging configuration from YAML file + if args.logging_config_path and os.path.exists(args.logging_config_path): + try: + with open(args.logging_config_path, "r") as f: + config = yaml.safe_load(f) + logging.config.dictConfig(config) + console.info(f"Loaded logging configuration from {args.logging_config_path}") + except Exception as e: + console.warning(f"Failed to load logging configuration from {args.logging_config_path}: {str(e)}") # Silence noisy third-party libraries logging.getLogger("urllib3").setLevel(logging.WARNING) @@ -170,7 +169,7 @@ def render(args, run_state: RunState, codeplain_api, event_bus: EventBus): # no template_dirs = file_utils.get_template_directories(args.filename, args.template_dir, DEFAULT_TEMPLATE_DIRS) - _, plain_source, _ = plain_file.plain_file_parser(args.filename, template_dirs) + _, plain_source, _ = plain_file.plain_file_parser(args.filename, template_dirs) if args.render_range is not None: args.render_range = get_render_range(args.render_range, plain_source) @@ -194,7 +193,32 @@ def render(args, run_state: RunState, codeplain_api, event_bus: EventBus): # no if args.api: codeplainAPI.api_url = args.api - module_renderer = ModuleRenderer( + console.info(f"Rendering {args.filename} to target code.") + + plain_source_tree = codeplainAPI.get_plain_source_tree(plain_source, loaded_templates, run_state) + + if args.render_range is not None: + args.render_range = get_render_range(args.render_range, plain_source) + elif args.render_from is not None: + args.render_range = get_render_range_from(args.render_from, plain_source) + + # Handle dry run and full plain here (outside of state machine) + if args.dry_run: + console.info("Printing dry run output...") + print_dry_run_output(plain_source, args.render_range) + return + + if args.full_plain: + console.info("Printing full plain output...") + console.info(plain_source) + return + + codeplainAPI = codeplain_api.CodeplainAPI(args.api_key, console) + codeplainAPI.verbose = args.verbose + assert args.api is not None and args.api != "", "API URL is required" + codeplainAPI.api_url = args.api + + module_renderer = ModuleRenderer( codeplainAPI, args.filename, args.render_range, @@ -223,23 +247,13 @@ def render(args, run_state: RunState, codeplain_api, event_bus: EventBus): # no return -def main(): # noqa: C901 +def main(): args = parse_arguments() event_bus = EventBus() setup_logging(event_bus, args.log_to_file, args.log_file_name, args.filename) - codeplain_api_module_name = "codeplain_local_api" - - codeplain_api_spec = importlib.util.find_spec(codeplain_api_module_name) - if args.api or codeplain_api_spec is None: - if not args.api: - args.api = "https://api.codeplain.ai" - import codeplain_REST_api as codeplain_api - else: - codeplain_api = importlib.import_module(codeplain_api_module_name) - run_state = RunState(spec_filename=args.filename, replay_with=args.replay_with) try: @@ -276,7 +290,6 @@ def main(): # noqa: C901 console.error(f"Error rendering plain code: {str(e)}\n") console.debug(f"Render ID: {run_state.render_id}") traceback.print_exc() - dump_crash_logs(args) if __name__ == "__main__": # noqa: C901 diff --git a/plain2code_arguments.py b/plain2code_arguments.py index 3fb3bc3..35c7db4 100644 --- a/plain2code_arguments.py +++ b/plain2code_arguments.py @@ -276,6 +276,13 @@ def create_parser(): help="If set, render the state machine graph.", ) + parser.add_argument( + "--logging-config-path", + action="store_true", + default="logging_config.yaml", + help="Path to the logging configuration file.", + ) + return parser diff --git a/pyproject.toml b/pyproject.toml index ff8b442..ab646b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,107 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "codeplain" +version = "0.1.1" +description = "Transform plain language specifications into working code" +readme = "README.md" +requires-python = ">=3.11" +classifiers = [ + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.11", + "Topic :: Software Development :: Code Generators", +] +dependencies = [ + "python-liquid2==0.3.0", + "mistletoe==1.3.0", + "langchain==1.0.8", + "langchain-core==1.2.3", + "langchain-openai==1.0.3", + "langchain-anthropic==1.1.0", + "langchain-aws==1.0.0", + "langchain-cerebras==0.8.0", + "langchain-google-genai==4.1.2", + "langchain-ollama==1.0.0", + "requests==2.32.3", + "langsmith==0.4.4", + "rich==14.2.0", + "tiktoken==0.12.0", + "PyYAML==6.0.2", + "gitpython==3.1.42", + "lizard==1.18.0", + "textual==1.0.0", + "SQLAlchemy==2.0.36", + "psycopg2==2.9.10", + "python-dotenv==1.1.0", + "transitions==0.9.3", + "cryptography==46.0.1", +] + +[project.optional-dependencies] +dev = [ + "pytest==8.3.5", + "flake8==7.0.0", + "black==24.2.0", + "isort==5.13.2", + "flake8-bugbear==24.12.12", + "flake8-unused-arguments==0.0.13", + "flake8-eradicate==1.5.0", + "pep8-naming==0.15.1", + "mypy==1.11.2", +] + +[project.scripts] +codeplain = "plain2code:main" + +[tool.setuptools] +py-modules = [ + "plain2code", + "plain2code_arguments", + "plain2code_console", + "plain2code_events", + "plain2code_exceptions", + "plain2code_nodes", + "plain2code_read_config", + "plain2code_state", + "plain2code_tui", + "plain2code_utils", + "plain_file", + "plain_spec", + "codeplain_constants", + "codeplain_local_api", + "codeplain_models", + "codeplain_REST_api", + "codeplain_types", + "codeplain_utils", + "codeplain", + "code_complexity", + "config", + "content_extractor", + "event_bus", + "file_utils", + "git_utils", + "hash_key", + "llm_exceptions", + "llm_handler", + "llm_selector", + "llm", + "render_cache", + "spinner", + "system_config", + "tui_components", +] + +[tool.setuptools.packages.find] +include = ["config*", "patch*", "prompt_templates*", "render_machine*", "repositories*", "services*", "standard_template_library*"] + +[tool.setuptools.package-data] +"*" = ["*.yaml", "*.css", "*.plain"] + [tool.black] line-length = 120 target-version = ['py311'] @@ -19,11 +123,6 @@ line_length = 120 skip = [".git", ".venv", "dist", "venv", ".conda", "tests/data"] skip_gitignore = true -[tool.pytest.ini_options] -pythonpath = ["src"] -testpaths = ["tests"] -norecursedirs = ["tests/data"] - [tool.mypy] python_version = "3.11" ignore_missing_imports = true @@ -61,3 +160,8 @@ exclude = [ "^src/services/langsmith/", "^deploy/", ] + +[tool.pytest.ini_options] +pythonpath = ["src"] +testpaths = ["tests"] +norecursedirs = ["tests/data"] \ No newline at end of file diff --git a/standard_template_library/__init__.py b/standard_template_library/__init__.py new file mode 100644 index 0000000..35e9cd5 --- /dev/null +++ b/standard_template_library/__init__.py @@ -0,0 +1 @@ +# Standard template library for plain2code diff --git a/system_config.py b/system_config.py index a9a42a2..8af040f 100644 --- a/system_config.py +++ b/system_config.py @@ -1,4 +1,4 @@ -import os +import importlib.resources import shutil import sys @@ -22,9 +22,9 @@ def __init__(self): def _load_config(self): """Load system configuration from YAML file.""" - config_path = os.path.join(os.path.dirname(__file__), "system_config.yaml") + config_path = importlib.resources.files("config").joinpath("system_config.yaml") try: - with open(config_path, "r") as f: + with config_path.open("r") as f: yaml_data = yaml.safe_load(f) return yaml_data except Exception as e: From 2b69869b3dfa36453f0d09c57cad9bed4c2ed292 Mon Sep 17 00:00:00 2001 From: zanjonke Date: Tue, 13 Jan 2026 13:04:51 +0100 Subject: [PATCH 2/6] Adding github action to push to PyPi on every release. --- .github/workflows/publish-to-pypi.yml | 86 +++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 .github/workflows/publish-to-pypi.yml diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml new file mode 100644 index 0000000..042f655 --- /dev/null +++ b/.github/workflows/publish-to-pypi.yml @@ -0,0 +1,86 @@ +name: Publish to PyPI + +on: + release: + types: [published] + +jobs: + build: + name: Build distribution + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Extract version from tag + id: get_version + run: | + # Strip 'v' prefix from tag (v0.2.1 -> 0.2.1) + VERSION=${GITHUB_REF_NAME#v} + echo "VERSION=$VERSION" >> $GITHUB_OUTPUT + echo "Extracted version: $VERSION" + + - name: Update version in pyproject.toml + run: | + sed -i "s/^version = .*/version = \"${{ steps.get_version.outputs.VERSION }}\"/" pyproject.toml + echo "Updated pyproject.toml:" + grep "^version" pyproject.toml + + - name: Commit version update to repo + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add pyproject.toml + git commit -m "Bump version to ${{ steps.get_version.outputs.VERSION }}" + git push origin HEAD:main + + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + pip install build + + - name: Build package + run: python -m build + + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + - name: Upload assets to GitHub Release + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release upload ${{ github.ref_name }} dist/* --clobber + + publish-to-pypi: + name: Publish to PyPI + needs: build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/codeplain + + steps: + - name: Download distribution packages + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + - name: Install twine + run: pip install twine + + - name: Publish to PyPI + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: twine upload dist/* From 5b257c272ff692810d16ffec83f831504b8f26e0 Mon Sep 17 00:00:00 2001 From: zanjonke Date: Wed, 21 Jan 2026 15:11:12 +0100 Subject: [PATCH 3/6] Update packaging --- plain2code.py | 65 ++++++++++---------------------------------------- pyproject.toml | 18 ++++++++------ 2 files changed, 23 insertions(+), 60 deletions(-) diff --git a/plain2code.py b/plain2code.py index 677e2d3..59c93db 100644 --- a/plain2code.py +++ b/plain2code.py @@ -89,6 +89,7 @@ def _get_frids_range(plain_source, start, end=None): def setup_logging( + args, event_bus: EventBus, log_to_file: bool, log_file_name: str, @@ -109,15 +110,15 @@ def setup_logging( log_file_path = get_log_file_path(plain_file_path, log_file_name) - # Try to load logging configuration from YAML file - if args.logging_config_path and os.path.exists(args.logging_config_path): - try: - with open(args.logging_config_path, "r") as f: - config = yaml.safe_load(f) - logging.config.dictConfig(config) - console.info(f"Loaded logging configuration from {args.logging_config_path}") - except Exception as e: - console.warning(f"Failed to load logging configuration from {args.logging_config_path}: {str(e)}") + # Try to load logging configuration from YAML file + if args.logging_config_path and os.path.exists(args.logging_config_path): + try: + with open(args.logging_config_path, "r") as f: + config = yaml.safe_load(f) + logging.config.dictConfig(config) + console.info(f"Loaded logging configuration from {args.logging_config_path}") + except Exception as e: + console.warning(f"Failed to load logging configuration from {args.logging_config_path}: {str(e)}") # Silence noisy third-party libraries logging.getLogger("urllib3").setLevel(logging.WARNING) @@ -169,56 +170,14 @@ def render(args, run_state: RunState, codeplain_api, event_bus: EventBus): # no template_dirs = file_utils.get_template_directories(args.filename, args.template_dir, DEFAULT_TEMPLATE_DIRS) - _, plain_source, _ = plain_file.plain_file_parser(args.filename, template_dirs) - - if args.render_range is not None: - args.render_range = get_render_range(args.render_range, plain_source) - elif args.render_from is not None: - args.render_range = get_render_range_from(args.render_from, plain_source) - - # Handle dry run and full plain here (outside of state machine) - if args.dry_run: - console.info("Printing dry run output...") - print_dry_run_output(plain_source, args.render_range) - return - - if args.full_plain: - console.info("Printing full plain output...") - console.info(plain_source) - return - - codeplainAPI = codeplain_api.CodeplainAPI(args.api_key, console) - codeplainAPI.verbose = args.verbose - - if args.api: - codeplainAPI.api_url = args.api - console.info(f"Rendering {args.filename} to target code.") - plain_source_tree = codeplainAPI.get_plain_source_tree(plain_source, loaded_templates, run_state) - - if args.render_range is not None: - args.render_range = get_render_range(args.render_range, plain_source) - elif args.render_from is not None: - args.render_range = get_render_range_from(args.render_from, plain_source) - - # Handle dry run and full plain here (outside of state machine) - if args.dry_run: - console.info("Printing dry run output...") - print_dry_run_output(plain_source, args.render_range) - return - - if args.full_plain: - console.info("Printing full plain output...") - console.info(plain_source) - return - codeplainAPI = codeplain_api.CodeplainAPI(args.api_key, console) codeplainAPI.verbose = args.verbose assert args.api is not None and args.api != "", "API URL is required" codeplainAPI.api_url = args.api - module_renderer = ModuleRenderer( + module_renderer = ModuleRenderer( codeplainAPI, args.filename, args.render_range, @@ -252,7 +211,7 @@ def main(): event_bus = EventBus() - setup_logging(event_bus, args.log_to_file, args.log_file_name, args.filename) + setup_logging(args, event_bus, args.log_to_file, args.log_file_name, args.filename) run_state = RunState(spec_filename=args.filename, replay_with=args.replay_with) diff --git a/pyproject.toml b/pyproject.toml index ab646b6..4d33127 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,16 +4,14 @@ build-backend = "setuptools.build_meta" [project] name = "codeplain" -version = "0.1.1" +version = "0.1.3" description = "Transform plain language specifications into working code" readme = "README.md" -requires-python = ">=3.11" +requires-python = ">=3.9" classifiers = [ "Environment :: Console", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3.11", + "Intended Audience :: Developers", + "Operating System :: OS Independent", "Topic :: Software Development :: Code Generators", ] dependencies = [ @@ -40,6 +38,8 @@ dependencies = [ "python-dotenv==1.1.0", "transitions==0.9.3", "cryptography==46.0.1", + "python-frontmatter==1.1.0", + "networkx==3.6.1" ] [project.optional-dependencies] @@ -94,10 +94,14 @@ py-modules = [ "spinner", "system_config", "tui_components", + "concept_utils", + "module_renderer", + "plain_modules", + "plain2code_logger", ] [tool.setuptools.packages.find] -include = ["config*", "patch*", "prompt_templates*", "render_machine*", "repositories*", "services*", "standard_template_library*"] +include = ["config*", "patch*", "prompt_templates*", "render_machine*", "repositories*", "services*", "standard_template_library*", "tui*"] [tool.setuptools.package-data] "*" = ["*.yaml", "*.css", "*.plain"] From 0465209b8ec770f8d34bde53b767cf3124225e6e Mon Sep 17 00:00:00 2001 From: Tjaz Erzen Date: Thu, 22 Jan 2026 10:53:59 +0100 Subject: [PATCH 4/6] Update plain2code API base URL defaults --- plain2code.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plain2code.py b/plain2code.py index 59c93db..74db27e 100644 --- a/plain2code.py +++ b/plain2code.py @@ -213,6 +213,9 @@ def main(): setup_logging(args, event_bus, args.log_to_file, args.log_file_name, args.filename) + if not args.api: + args.api = "https://api.codeplain.ai" + run_state = RunState(spec_filename=args.filename, replay_with=args.replay_with) try: From 847e7cc66a67f73aab0c43b05d6c31144aa69cc3 Mon Sep 17 00:00:00 2001 From: zanjonke Date: Fri, 23 Jan 2026 10:04:50 +0100 Subject: [PATCH 5/6] Removing unnecessary dependencies --- install.sh | 217 +++++++++++++++++++++++++++++++++++++++++++++++++ plain2code.py | 14 +--- pyproject.toml | 24 ++---- 3 files changed, 224 insertions(+), 31 deletions(-) create mode 100755 install.sh diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..03edca7 --- /dev/null +++ b/install.sh @@ -0,0 +1,217 @@ +#!/bin/bash + +set -e + +# Brand Colors (True Color / 24-bit) +YELLOW='\033[38;2;224;255;110m' # #E0FF6E +GREEN='\033[38;2;121;252;150m' # #79FC96 +GREEN_LIGHT='\033[38;2;197;220;217m' # #C5DCD9 +GREEN_DARK='\033[38;2;34;57;54m' # #223936 +BLUE='\033[38;2;10;31;212m' # #0A1FD4 +BLACK='\033[38;2;26;26;26m' # #1A1A1A +WHITE='\033[38;2;255;255;255m' # #FFFFFF +RED='\033[38;2;239;68;68m' # #EF4444 +GRAY='\033[38;2;128;128;128m' # #808080 +GRAY_LIGHT='\033[38;2;211;211;211m' # #D3D3D3 +BOLD='\033[1m' +NC='\033[0m' # No Color / Reset + +# Required Python version +REQUIRED_MAJOR=3 +REQUIRED_MINOR=11 + +# Detect OS +detect_os() { + if [[ "$OSTYPE" == "darwin"* ]]; then + echo "macos" + elif [[ -f /etc/debian_version ]]; then + echo "debian" + elif [[ -f /etc/redhat-release ]]; then + echo "redhat" + else + echo "unknown" + fi +} + +echo -e "started ${YELLOW}${BOLD}*codeplain CLI${NC} installation..." + +# Install Python based on OS +install_python() { + local os=$(detect_os) + + case $os in + macos) + if command -v brew &> /dev/null; then + echo -e "installing Python ${REQUIRED_MAJOR}.${REQUIRED_MINOR} via Homebrew..." + brew install python@${REQUIRED_MAJOR}.${REQUIRED_MINOR} + else + echo -e "${RED}Error: Homebrew is not installed.${NC}" + echo "please install Homebrew first: https://brew.sh" + echo "or install Python manually from: https://www.python.org/downloads/" + exit 1 + fi + ;; + debian) + echo -e "installing Python ${REQUIRED_MAJOR}.${REQUIRED_MINOR} via apt..." + sudo apt update + sudo apt install -y python${REQUIRED_MAJOR}.${REQUIRED_MINOR} python${REQUIRED_MAJOR}.${REQUIRED_MINOR}-venv python3-pip + ;; + redhat) + echo -e "installing Python ${REQUIRED_MAJOR}.${REQUIRED_MINOR} via dnf..." + sudo dnf install -y python${REQUIRED_MAJOR}.${REQUIRED_MINOR} + ;; + *) + echo -e "${RED}Error: Automatic installation not supported for your OS.${NC}" + echo "please install Python ${REQUIRED_MAJOR}.${REQUIRED_MINOR} manually from:" + echo " https://www.python.org/downloads/" + exit 1 + ;; + esac +} + +# Prompt user to install Python +prompt_install_python() { + echo "" + read -p "$(echo -e ${YELLOW}would you like to install Python ${REQUIRED_MAJOR}.${REQUIRED_MINOR}? \(Y/n\): ${NC})" response + case "$response" in + [yY][eE][sS]|[yY]|"") + install_python + echo "" + echo -e "${GREEN}✓ python installed.${NC} please restart your terminal and run this script again." + exit 0 + ;; + *) + echo -e "${YELLOW}installation cancelled.${NC}" + exit 1 + ;; + esac +} + +# Check if python3 or python is installed +if command -v python3.11 &> /dev/null; then + PYTHON_CMD="python3.11" +elif command -v python3 &> /dev/null; then + PYTHON_CMD="python3" +elif command -v python &> /dev/null; then + PYTHON_CMD="python" +else + echo -e "${RED}error: Python 3 is not installed.${NC}" + prompt_install_python +fi + +# Get Python version +PYTHON_VERSION=$($PYTHON_CMD -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') +PYTHON_MAJOR=$(echo "$PYTHON_VERSION" | cut -d. -f1) +PYTHON_MINOR=$(echo "$PYTHON_VERSION" | cut -d. -f2) + +# Check version +if [ "$PYTHON_MAJOR" -lt "$REQUIRED_MAJOR" ] || \ + ([ "$PYTHON_MAJOR" -eq "$REQUIRED_MAJOR" ] && [ "$PYTHON_MINOR" -lt "$REQUIRED_MINOR" ]); then + echo -e "${RED}error: Python ${REQUIRED_MAJOR}.${REQUIRED_MINOR} or greater is required.${NC}" + echo -e "found: python ${YELLOW}${PYTHON_VERSION}${NC}" + prompt_install_python +fi + +echo -e "" +echo -e "${GREEN}✓${NC} python ${BOLD}${PYTHON_VERSION}${NC} detected" +echo -e "" +# Use python -m pip for reliability +PIP_CMD="$PYTHON_CMD -m pip" + +# Install or upgrade codeplain +if $PIP_CMD show codeplain &> /dev/null; then + CURRENT_VERSION=$($PIP_CMD show codeplain | grep "^Version:" | cut -d' ' -f2) + echo -e "${GRAY}codeplain ${CURRENT_VERSION} is already installed.${NC}" + echo -e "upgrading to latest version..." + echo -e "" + $PIP_CMD install --upgrade codeplain &> /dev/null + NEW_VERSION=$($PIP_CMD show codeplain | grep "^Version:" | cut -d' ' -f2) + if [ "$CURRENT_VERSION" = "$NEW_VERSION" ]; then + echo -e "${GREEN}✓${NC} codeplain is already up to date (${NEW_VERSION})" + else + echo -e "${GREEN}✓${NC} codeplain upgraded from ${CURRENT_VERSION} to ${NEW_VERSION}!" + fi +else + echo -e "installing codeplain...${NC}" + echo -e "" + $PIP_CMD install codeplain &> /dev/null + echo -e "${GREEN}✓ codeplain installed successfully!${NC}" +fi + +echo -e "${GREEN}✓${NC} the latest version of *codeplain CLI is now installed." +echo "" +echo -e "go to ${YELLOW}https://platform.codeplain.ai${NC} and sign up to get your API key." +echo "" +read -p "paste your API key here: " API_KEY +echo "" + +if [ -z "$API_KEY" ]; then + echo -e "${GRAY}no API key provided. you can set it later with:${NC}" + echo -e " export CODEPLAIN_API_KEY=\"your_api_key\"" +else + # Export for current session + export CODEPLAIN_API_KEY="$API_KEY" + + # Detect user's default shell from $SHELL (works even when script runs in different shell) + case "$SHELL" in + */zsh) + SHELL_RC="$HOME/.zprofile" + ;; + */bash) + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS uses .bash_profile for login shells + SHELL_RC="$HOME/.bash_profile" + else + SHELL_RC="$HOME/.bashrc" + fi + ;; + *) + SHELL_RC="$HOME/.profile" + ;; + esac + + # Create the file if it doesn't exist + touch "$SHELL_RC" + + # Add to shell config if not already present + if ! grep -q "CODEPLAIN_API_KEY" "$SHELL_RC" 2>/dev/null; then + echo "" >> "$SHELL_RC" + echo "# codeplain API Key" >> "$SHELL_RC" + echo "export CODEPLAIN_API_KEY=\"$API_KEY\"" >> "$SHELL_RC" + echo -e "${GREEN}✓ API key saved to ${SHELL_RC}${NC}" + else + # Update existing key (different sed syntax for macOS vs Linux) + if [[ "$OSTYPE" == "darwin"* ]]; then + sed -i '' "s|export CODEPLAIN_API_KEY=.*|export CODEPLAIN_API_KEY=\"$API_KEY\"|" "$SHELL_RC" + else + sed -i "s|export CODEPLAIN_API_KEY=.*|export CODEPLAIN_API_KEY=\"$API_KEY\"|" "$SHELL_RC" + fi + echo -e "${GREEN}✓${NC} API key added to ${SHELL_RC}" + fi + +fi + +# ASCII Art Welcome +echo "" +echo -e "${NC}" +echo -e "${GRAY}────────────────────────────────────────────${NC}" +echo -e "" +cat << 'EOF' + _ _ _ + ___ ___ __| | ___ _ __ | | __ _(_)_ __ + / __/ _ \ / _` |/ _ \ '_ \| |/ _` | | '_ \ + | (_| (_) | (_| | __/ |_) | | (_| | | | | | + \___\___/ \__,_|\___| .__/|_|\__,_|_|_| |_| + |_| +EOF +echo "" +echo -e " ${YELLOW}welcome to *codeplain!${NC}" +echo "" +echo -e " spec-driven, production-ready code generation" +echo "" +echo "" +echo -e "${GRAY}────────────────────────────────────────────${NC}" +echo "" +echo -e " thank you for using *codeplain!" +echo "" +echo -e " run '${YELLOW}${BOLD}codeplain ${NC}' to get started." diff --git a/plain2code.py b/plain2code.py index 74db27e..43a1769 100644 --- a/plain2code.py +++ b/plain2code.py @@ -27,7 +27,6 @@ get_log_file_path, ) from plain2code_state import RunState -from plain2code_utils import print_dry_run_output from system_config import system_config from tui.plain2code_tui import Plain2CodeTUI @@ -107,6 +106,8 @@ def setup_logging( logging.getLogger("services.langsmith.langsmith_service").setLevel(logging.WARNING) logging.getLogger("transitions").setLevel(logging.WARNING) logging.getLogger("repositories").setLevel(logging.WARNING) + logging.getLogger("google_genai").setLevel(logging.WARNING) + logging.getLogger("openai").setLevel(logging.WARNING) log_file_path = get_log_file_path(plain_file_path, log_file_name) @@ -120,17 +121,6 @@ def setup_logging( except Exception as e: console.warning(f"Failed to load logging configuration from {args.logging_config_path}: {str(e)}") - # Silence noisy third-party libraries - logging.getLogger("urllib3").setLevel(logging.WARNING) - logging.getLogger("httpx").setLevel(logging.WARNING) - logging.getLogger("httpcore").setLevel(logging.WARNING) - logging.getLogger("anthropic").setLevel(logging.WARNING) - logging.getLogger("langsmith").setLevel(logging.WARNING) - logging.getLogger("git").setLevel(logging.WARNING) - logging.getLogger("openai").setLevel(logging.WARNING) - logging.getLogger("transitions").setLevel(logging.WARNING) - logging.getLogger("google_genai").setLevel(logging.WARNING) - # Allow detailed retry logs for anthropic if needed logging.getLogger("anthropic._base_client").setLevel(logging.DEBUG) if logging.getLogger("anthropic._base_client").level == logging.DEBUG: diff --git a/pyproject.toml b/pyproject.toml index 4d33127..b18e986 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta" [project] name = "codeplain" -version = "0.1.3" +version = "0.1.6" description = "Transform plain language specifications into working code" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.11" classifiers = [ "Environment :: Console", "Intended Audience :: Developers", @@ -17,27 +17,13 @@ classifiers = [ dependencies = [ "python-liquid2==0.3.0", "mistletoe==1.3.0", - "langchain==1.0.8", - "langchain-core==1.2.3", - "langchain-openai==1.0.3", - "langchain-anthropic==1.1.0", - "langchain-aws==1.0.0", - "langchain-cerebras==0.8.0", - "langchain-google-genai==4.1.2", - "langchain-ollama==1.0.0", - "requests==2.32.3", - "langsmith==0.4.4", - "rich==14.2.0", + "requests==2.32.3", "tiktoken==0.12.0", "PyYAML==6.0.2", "gitpython==3.1.42", - "lizard==1.18.0", - "textual==1.0.0", - "SQLAlchemy==2.0.36", - "psycopg2==2.9.10", - "python-dotenv==1.1.0", "transitions==0.9.3", - "cryptography==46.0.1", + "textual==1.0.0", + "rich==14.2.0", "python-frontmatter==1.1.0", "networkx==3.6.1" ] From d5cf4bff679cf81913bf0ce639f8f81ee1565607 Mon Sep 17 00:00:00 2001 From: zanjonke Date: Fri, 23 Jan 2026 17:32:45 +0100 Subject: [PATCH 6/6] fixup! Removing unnecessary dependencies --- config/__init__.py | 1 - plain2code.py | 6 +++--- pyproject.toml | 4 ---- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/config/__init__.py b/config/__init__.py index d8f6573..08701da 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -1,2 +1 @@ # Configuration files for plain2code - diff --git a/plain2code.py b/plain2code.py index 43a1769..b4e2cd7 100644 --- a/plain2code.py +++ b/plain2code.py @@ -104,10 +104,9 @@ def setup_logging( logging.getLogger("git").setLevel(logging.WARNING) logging.getLogger("anthropic._base_client").setLevel(logging.WARNING) logging.getLogger("services.langsmith.langsmith_service").setLevel(logging.WARNING) - logging.getLogger("transitions").setLevel(logging.WARNING) logging.getLogger("repositories").setLevel(logging.WARNING) - logging.getLogger("google_genai").setLevel(logging.WARNING) - logging.getLogger("openai").setLevel(logging.WARNING) + logging.getLogger("transitions").setLevel(logging.ERROR) + logging.getLogger("transitions.extensions.diagrams").setLevel(logging.ERROR) log_file_path = get_log_file_path(plain_file_path, log_file_name) @@ -242,6 +241,7 @@ def main(): console.error(f"Error rendering plain code: {str(e)}\n") console.debug(f"Render ID: {run_state.render_id}") traceback.print_exc() + dump_crash_logs(args) if __name__ == "__main__": # noqa: C901 diff --git a/pyproject.toml b/pyproject.toml index b18e986..08dd61a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,10 +34,6 @@ dev = [ "flake8==7.0.0", "black==24.2.0", "isort==5.13.2", - "flake8-bugbear==24.12.12", - "flake8-unused-arguments==0.0.13", - "flake8-eradicate==1.5.0", - "pep8-naming==0.15.1", "mypy==1.11.2", ]