From 3adc5a8c1da1ffe031ccfedb9c166811b1a4e601 Mon Sep 17 00:00:00 2001 From: YusufKaraaslanSpyke Date: Mon, 23 Feb 2026 20:56:13 +0300 Subject: [PATCH 1/4] fix: unify scraper argument interface and fix create command forwarding All scrapers (scrape, github, analyze, pdf) now share a common argument contract via add_all_standard_arguments() in arguments/common.py. Universal flags (--dry-run, --verbose, --quiet, --name, --description, workflow args) work consistently across all source types. Previously, `create --dry-run`, `create owner/repo --dry-run`, and `create ./path --dry-run` would crash because sub-scrapers didn't accept those flags. Also fixes main.py _handle_analyze_command() not forwarding --dry-run, --preset, --quiet, --name, --description to codebase_scraper. Co-Authored-By: Claude Opus 4.6 --- CHANGELOG.md | 21 +++ CLAUDE.md | 11 +- docs/reference/CLI_REFERENCE.md | 56 ++++-- src/skill_seekers/cli/arguments/analyze.py | 88 ++++----- src/skill_seekers/cli/arguments/common.py | 93 ++++++++-- src/skill_seekers/cli/arguments/github.py | 103 +++-------- src/skill_seekers/cli/arguments/pdf.py | 104 ++++------- src/skill_seekers/cli/arguments/scrape.py | 129 ++++---------- src/skill_seekers/cli/codebase_scraper.py | 198 ++++++--------------- src/skill_seekers/cli/create_command.py | 67 ++++--- src/skill_seekers/cli/github_scraper.py | 23 +++ src/skill_seekers/cli/main.py | 24 +++ src/skill_seekers/cli/pdf_scraper.py | 19 ++ 13 files changed, 431 insertions(+), 505 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c30d81..0235dc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ All notable changes to Skill Seeker will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.1.2] - 2026-02-23 + +### 🔧 Fix `create` Command Argument Forwarding & Unify Scraper Interface + +### Fixed +- **`create` command argument forwarding** — Universal flags (`--dry-run`, `--verbose`, `--quiet`, `--name`, `--description`) now work correctly across all source types. Previously, `create -p quick --dry-run`, `create owner/repo --dry-run`, and `create ./path --dry-run` would crash because sub-scrapers didn't accept those flags +- **`skill-seekers analyze --dry-run`** — Fixed `_handle_analyze_command()` in `main.py` not forwarding `--dry-run`, `--preset`, `--quiet`, `--name`, `--description`, `--api-key`, and workflow flags to codebase_scraper + +### Added +- **Shared argument contract** — New `add_all_standard_arguments(parser)` in `arguments/common.py` registers common + behavior + workflow args on any parser as a single call +- **`BEHAVIOR_ARGUMENTS`** — Centralized `--dry-run`, `--verbose`, `--quiet` definitions in `arguments/common.py` +- **`--dry-run` for GitHub scraper** — `skill-seekers github --repo owner/repo --dry-run` now previews the operation +- **`--dry-run` for PDF scraper** — `skill-seekers pdf --name test --dry-run` now previews the operation +- **`--verbose`/`--quiet` for GitHub and PDF scrapers** — Logging level control now works consistently across all scrapers +- **`--name`/`--description` for codebase analyzer** — Custom skill name and description can now be passed to `skill-seekers analyze` + +### Changed +- **Argument deduplication** — Removed duplicated argument definitions from `arguments/github.py`, `arguments/scrape.py`, `arguments/analyze.py`, `arguments/pdf.py`; all now import shared args from `arguments/common.py` +- **`create` command `_add_common_args()`** — Only forwards truly universal flags; route-specific flags (`--preset`, `--config`, `--chunk-for-rag`, etc.) moved to their respective route methods +- **`codebase_scraper.py` argparser** — Replaced ~190 lines of inline argparser with `add_analyze_arguments(parser)` call + ## [3.1.1] - 2026-02-23 ### 🐛 Hotfix diff --git a/CLAUDE.md b/CLAUDE.md index 85d0665..32c3d87 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co **Skill Seekers** is the **universal documentation preprocessor** for AI systems. It transforms documentation websites, GitHub repositories, and PDFs into production-ready formats for **16+ platforms**: RAG pipelines (LangChain, LlamaIndex, Haystack), vector databases (Pinecone, Chroma, Weaviate, FAISS, Qdrant), AI coding assistants (Cursor, Windsurf, Cline, Continue.dev), and LLM platforms (Claude, Gemini, OpenAI). -**Current Version:** v3.1.0-dev +**Current Version:** v3.1.2-dev **Python Version:** 3.10+ required **Status:** Production-ready, published on PyPI **Website:** https://skillseekersweb.com/ - Browse configs, share, and access documentation @@ -2256,7 +2256,14 @@ The `scripts/` directory contains utility scripts: ## 🎉 Recent Achievements -**v3.1.0 (In Development) - "Unified CLI & Developer Experience":** +**v3.1.2 (In Development) - "Unified Argument Interface":** +- 🔧 **Unified Scraper Arguments** - All scrapers (scrape, github, analyze, pdf) now share a common argument contract via `add_all_standard_arguments(parser)` in `arguments/common.py` +- 🐛 **Fix `create` Argument Forwarding** - `create --dry-run`, `create owner/repo --dry-run`, `create ./path --dry-run` all work now (previously crashed) +- đŸ—ī¸ **Argument Deduplication** - Removed duplicated arg definitions from github.py, scrape.py, analyze.py, pdf.py; all import shared args +- ➕ **New Flags** - GitHub and PDF scrapers gain `--dry-run`, `--verbose`, `--quiet`; analyze gains `--name`, `--description`, `--quiet` +- 🔀 **Route-Specific Forwarding** - `create` command's `_add_common_args()` now only forwards universal flags; route-specific flags moved to their respective methods + +**v3.1.0 - "Unified CLI & Developer Experience":** - đŸŽ¯ **Unified `create` Command** - Auto-detects source type (web/GitHub/local/PDF/config) - 📋 **Progressive Disclosure Help** - Default shows 13 universal flags, detailed help available per source - ⚡ **-p Shortcut** - Quick preset selection (`-p quick|standard|comprehensive`) diff --git a/docs/reference/CLI_REFERENCE.md b/docs/reference/CLI_REFERENCE.md index 754752e..7fac99b 100644 --- a/docs/reference/CLI_REFERENCE.md +++ b/docs/reference/CLI_REFERENCE.md @@ -1,7 +1,7 @@ # CLI Reference - Skill Seekers -> **Version:** 3.1.0 -> **Last Updated:** 2026-02-16 +> **Version:** 3.1.2 +> **Last Updated:** 2026-02-23 > **Complete reference for all 20 CLI commands** --- @@ -63,15 +63,21 @@ skill-seekers --version ### Global Flags -These flags work with most commands: +These flags work with **all scraper commands** (scrape, github, analyze, pdf, create): | Flag | Description | |------|-------------| | `-h, --help` | Show help message and exit | | `--version` | Show version number and exit | +| `-n, --name` | Skill name | +| `-d, --description` | Skill description | +| `-o, --output` | Output directory | +| `--enhance-level` | AI enhancement level (0-3) | +| `--api-key` | Anthropic API key | | `-v, --verbose` | Enable verbose (DEBUG) output | | `-q, --quiet` | Minimize output (WARNING only) | | `--dry-run` | Preview without executing | +| `--enhance-workflow` | Apply enhancement workflow preset | ### Environment Variables @@ -116,17 +122,21 @@ skill-seekers analyze --directory DIR [options] | Short | Long | Default | Description | |-------|------|---------|-------------| +| `-n` | `--name` | auto | Skill name (defaults to directory name) | +| `-d` | `--description` | auto | Skill description | | | `--preset` | standard | Analysis preset: quick, standard, comprehensive | | | `--preset-list` | | Show available presets and exit | | | `--languages` | auto | Comma-separated languages (Python,JavaScript,C++) | | | `--file-patterns` | | Comma-separated file patterns | -| | `--enhance-level` | 2 | AI enhancement: 0=off, 1=SKILL.md, 2=+config, 3=full | +| | `--enhance-level` | 0 | AI enhancement: 0=off (default), 1=SKILL.md, 2=+config, 3=full | | | `--api-key` | | Anthropic API key (or ANTHROPIC_API_KEY env) | | | `--enhance-workflow` | | Apply workflow preset (can use multiple) | | | `--enhance-stage` | | Add inline enhancement stage (name:prompt) | | | `--var` | | Override workflow variable (key=value) | | | `--workflow-dry-run` | | Preview workflow without executing | | | `--dry-run` | | Preview analysis without creating output | +| `-v` | `--verbose` | | Enable verbose (DEBUG) logging | +| `-q` | `--quiet` | | Minimize output (WARNING only) | | | `--skip-api-reference` | | Skip API docs generation | | | `--skip-dependency-graph` | | Skip dependency graph | | | `--skip-patterns` | | Skip pattern detection | @@ -135,7 +145,6 @@ skill-seekers analyze --directory DIR [options] | | `--skip-config-patterns` | | Skip config pattern extraction | | | `--skip-docs` | | Skip project docs (README) | | | `--no-comments` | | Skip comment extraction | -| `-v` | `--verbose` | | Enable verbose logging | **Examples:** @@ -427,6 +436,7 @@ skill-seekers github [options] | | `--token` | | GitHub personal access token | | `-n` | `--name` | auto | Skill name | | `-d` | `--description` | auto | Description | +| `-o` | `--output` | auto | Output directory | | | `--no-issues` | | Skip GitHub issues | | | `--no-changelog` | | Skip CHANGELOG | | | `--no-releases` | | Skip releases | @@ -437,6 +447,9 @@ skill-seekers github [options] | | `--enhance-workflow` | | Apply workflow preset | | | `--non-interactive` | | CI/CD mode (fail fast) | | | `--profile` | | GitHub profile from config | +| | `--dry-run` | | Preview without executing | +| `-v` | `--verbose` | | Enable verbose (DEBUG) logging | +| `-q` | `--quiet` | | Minimize output (WARNING only) | **Examples:** @@ -450,6 +463,9 @@ skill-seekers github --repo facebook/react --token $GITHUB_TOKEN # Skip issues for faster scraping skill-seekers github --repo facebook/react --no-issues +# Dry run to preview +skill-seekers github --repo facebook/react --dry-run + # Scrape only, build later skill-seekers github --repo facebook/react --scrape-only ``` @@ -659,18 +675,23 @@ skill-seekers pdf [options] **Flags:** -| Short | Long | Description | -|-------|------|-------------| -| `-c` | `--config` | PDF config JSON file | -| | `--pdf` | Direct PDF file path | -| `-n` | `--name` | Skill name | -| `-d` | `--description` | Description | -| | `--from-json` | Build from extracted JSON | -| | `--enhance-workflow` | Apply workflow preset | -| | `--enhance-stage` | Add inline stage | -| | `--var` | Override workflow variable | -| | `--workflow-dry-run` | Preview workflow | +| Short | Long | Default | Description | +|-------|------|---------|-------------| +| `-c` | `--config` | | PDF config JSON file | +| | `--pdf` | | Direct PDF file path | +| `-n` | `--name` | auto | Skill name | +| `-d` | `--description` | auto | Description | +| `-o` | `--output` | auto | Output directory | +| | `--from-json` | | Build from extracted JSON | | | `--enhance-level` | 0 | AI enhancement (default: 0 for PDF) | +| | `--api-key` | | Anthropic API key | +| | `--enhance-workflow` | | Apply workflow preset | +| | `--enhance-stage` | | Add inline stage | +| | `--var` | | Override workflow variable | +| | `--workflow-dry-run` | | Preview workflow | +| | `--dry-run` | | Preview without executing | +| `-v` | `--verbose` | | Enable verbose (DEBUG) logging | +| `-q` | `--quiet` | | Minimize output (WARNING only) | **Examples:** @@ -683,6 +704,9 @@ skill-seekers pdf --config configs/manual.json # Enable enhancement skill-seekers pdf --pdf manual.pdf --enhance-level 2 + +# Dry run to preview +skill-seekers pdf --pdf manual.pdf --name test --dry-run ``` --- diff --git a/src/skill_seekers/cli/arguments/analyze.py b/src/skill_seekers/cli/arguments/analyze.py index 093dae5..3cbc07f 100644 --- a/src/skill_seekers/cli/arguments/analyze.py +++ b/src/skill_seekers/cli/arguments/analyze.py @@ -5,13 +5,21 @@ Both codebase_scraper.py (standalone) and parsers/analyze_parser.py (unified CLI import and use these definitions. Includes preset system support for #268. + +Shared arguments (name, description, output, enhance-level, api-key, +dry-run, verbose, quiet, workflow args) come from common.py / workflow.py +via ``add_all_standard_arguments()``. """ import argparse from typing import Any -from .workflow import WORKFLOW_ARGUMENTS +from .common import add_all_standard_arguments +# Analyze-specific argument definitions as data structure +# NOTE: Shared args (name, description, output, enhance_level, api_key, dry_run, +# verbose, quiet, workflow args) are registered by add_all_standard_arguments(). +# The default enhance_level for analyze is 0 (overridden after registration). ANALYZE_ARGUMENTS: dict[str, dict[str, Any]] = { # Core options "directory": { @@ -23,15 +31,6 @@ ANALYZE_ARGUMENTS: dict[str, dict[str, Any]] = { "metavar": "DIR", }, }, - "output": { - "flags": ("--output",), - "kwargs": { - "type": str, - "default": "output/codebase/", - "help": "Output directory (default: output/codebase/)", - "metavar": "DIR", - }, - }, # Preset system (Issue #268) "preset": { "flags": ("--preset",), @@ -91,21 +90,6 @@ ANALYZE_ARGUMENTS: dict[str, dict[str, Any]] = { "metavar": "PATTERNS", }, }, - # Enhancement options - "enhance_level": { - "flags": ("--enhance-level",), - "kwargs": { - "type": int, - "choices": [0, 1, 2, 3], - "default": 2, - "help": ( - "AI enhancement level (auto-detects API vs LOCAL mode): " - "0=disabled, 1=SKILL.md only, 2=+architecture/config (default), 3=full enhancement. " - "Mode selection: uses API if ANTHROPIC_API_KEY is set, otherwise LOCAL (Claude Code)" - ), - "metavar": "LEVEL", - }, - }, # Feature skip options "skip_api_reference": { "flags": ("--skip-api-reference",), @@ -163,38 +147,32 @@ ANALYZE_ARGUMENTS: dict[str, dict[str, Any]] = { "help": "Skip comment extraction", }, }, - # Output options - "verbose": { - "flags": ("--verbose",), - "kwargs": { - "action": "store_true", - "help": "Enable verbose logging", - }, - }, - # Dry-run and API key (parity with scrape/github/pdf) - "dry_run": { - "flags": ("--dry-run",), - "kwargs": { - "action": "store_true", - "help": "Preview what will be analyzed without creating output", - }, - }, - "api_key": { - "flags": ("--api-key",), - "kwargs": { - "type": str, - "help": "Anthropic API key (or set ANTHROPIC_API_KEY env var)", - "metavar": "KEY", - }, - }, } -# Add workflow arguments (enhance_workflow, enhance_stage, var, workflow_dry_run, workflow_history) -ANALYZE_ARGUMENTS.update(WORKFLOW_ARGUMENTS) - def add_analyze_arguments(parser: argparse.ArgumentParser) -> None: - """Add all analyze command arguments to a parser.""" + """Add all analyze command arguments to a parser. + + Registers shared args (name, description, output, enhance-level, api-key, + dry-run, verbose, quiet, workflow args) via add_all_standard_arguments(), + then adds analyze-specific args on top. + + The default for --enhance-level is overridden to 0 (off) for analyze, + and --output default is set to 'output/codebase/'. + """ + # Shared universal args first + add_all_standard_arguments(parser) + + # Override defaults that differ for the analyze command + # enhance-level defaults to 0 (off) for codebase analysis + for action in parser._actions: + if hasattr(action, "dest"): + if action.dest == "enhance_level": + action.default = 0 + elif action.dest == "output": + action.default = "output/codebase/" + + # Analyze-specific args for arg_name, arg_def in ANALYZE_ARGUMENTS.items(): flags = arg_def["flags"] kwargs = arg_def["kwargs"] @@ -203,4 +181,6 @@ def add_analyze_arguments(parser: argparse.ArgumentParser) -> None: def get_analyze_argument_names() -> set: """Get the set of analyze argument destination names.""" - return set(ANALYZE_ARGUMENTS.keys()) + from .common import get_all_standard_argument_names + + return get_all_standard_argument_names() | set(ANALYZE_ARGUMENTS.keys()) diff --git a/src/skill_seekers/cli/arguments/common.py b/src/skill_seekers/cli/arguments/common.py index 2973fa5..7fbdb36 100644 --- a/src/skill_seekers/cli/arguments/common.py +++ b/src/skill_seekers/cli/arguments/common.py @@ -2,6 +2,14 @@ These arguments are used by most commands (scrape, github, pdf, analyze, etc.) and provide consistent behavior for configuration, output control, and help. + +Hierarchy: + COMMON_ARGUMENTS - Identity + enhancement (name, description, output, enhance-level, api-key) + BEHAVIOR_ARGUMENTS - Runtime behavior (dry-run, verbose, quiet) + WORKFLOW_ARGUMENTS - Enhancement workflows (from workflow.py) + + add_all_standard_arguments(parser) - Registers all three groups at once. + Every scraper should call this so the `create` command can forward flags safely. """ import argparse @@ -10,14 +18,6 @@ from typing import Any # Common argument definitions as data structure # These are arguments that appear in MULTIPLE commands COMMON_ARGUMENTS: dict[str, dict[str, Any]] = { - "config": { - "flags": ("--config", "-c"), - "kwargs": { - "type": str, - "help": "Load configuration from JSON file (e.g., configs/react.json)", - "metavar": "FILE", - }, - }, "name": { "flags": ("--name",), "kwargs": { @@ -66,6 +66,31 @@ COMMON_ARGUMENTS: dict[str, dict[str, Any]] = { }, } +# Behavior arguments — runtime flags shared by every scraper +BEHAVIOR_ARGUMENTS: dict[str, dict[str, Any]] = { + "dry_run": { + "flags": ("--dry-run",), + "kwargs": { + "action": "store_true", + "help": "Preview what will happen without actually executing", + }, + }, + "verbose": { + "flags": ("--verbose", "-v"), + "kwargs": { + "action": "store_true", + "help": "Enable verbose output (DEBUG level logging)", + }, + }, + "quiet": { + "flags": ("--quiet", "-q"), + "kwargs": { + "action": "store_true", + "help": "Minimize output (WARNING level logging only)", + }, + }, +} + # RAG (Retrieval-Augmented Generation) arguments # These are shared across commands that support RAG chunking RAG_ARGUMENTS: dict[str, dict[str, Any]] = { @@ -108,7 +133,7 @@ def add_common_arguments(parser: argparse.ArgumentParser) -> None: Example: >>> parser = argparse.ArgumentParser() >>> add_common_arguments(parser) - >>> # Now parser has --config, --name, --description, etc. + >>> # Now parser has --name, --description, etc. """ for arg_name, arg_def in COMMON_ARGUMENTS.items(): flags = arg_def["flags"] @@ -116,11 +141,33 @@ def add_common_arguments(parser: argparse.ArgumentParser) -> None: parser.add_argument(*flags, **kwargs) +def add_behavior_arguments(parser: argparse.ArgumentParser) -> None: + """Add behavior arguments (--dry-run, --verbose, --quiet) to a parser.""" + for arg_name, arg_def in BEHAVIOR_ARGUMENTS.items(): + flags = arg_def["flags"] + kwargs = arg_def["kwargs"] + parser.add_argument(*flags, **kwargs) + + +def add_all_standard_arguments(parser: argparse.ArgumentParser) -> None: + """Add common + behavior + workflow arguments to a parser. + + This is the ONE call every scraper should make to accept all universal flags + that the ``create`` command may forward. + """ + add_common_arguments(parser) + add_behavior_arguments(parser) + # Import here to avoid circular imports + from .workflow import add_workflow_arguments + + add_workflow_arguments(parser) + + def get_common_argument_names() -> set: """Get the set of common argument destination names. Returns: - Set of argument dest names (e.g., {'config', 'name', 'description', ...}) + Set of argument dest names (e.g., {'name', 'description', ...}) """ return set(COMMON_ARGUMENTS.keys()) @@ -153,16 +200,34 @@ def get_rag_argument_names() -> set: return set(RAG_ARGUMENTS.keys()) +def get_behavior_argument_names() -> set: + """Get the set of behavior argument destination names.""" + return set(BEHAVIOR_ARGUMENTS.keys()) + + +def get_all_standard_argument_names() -> set: + """Get the combined set of common + behavior + workflow dest names.""" + from .workflow import WORKFLOW_ARGUMENTS + + return ( + set(COMMON_ARGUMENTS.keys()) + | set(BEHAVIOR_ARGUMENTS.keys()) + | set(WORKFLOW_ARGUMENTS.keys()) + ) + + def get_argument_help(arg_name: str) -> str: - """Get the help text for a common argument. + """Get the help text for a common or behavior argument. Args: - arg_name: Name of the argument (e.g., 'config') + arg_name: Name of the argument (e.g., 'name', 'dry_run') Returns: Help text string Raises: - KeyError: If argument doesn't exist + KeyError: If argument doesn't exist in either dict """ - return COMMON_ARGUMENTS[arg_name]["kwargs"]["help"] + if arg_name in COMMON_ARGUMENTS: + return COMMON_ARGUMENTS[arg_name]["kwargs"]["help"] + return BEHAVIOR_ARGUMENTS[arg_name]["kwargs"]["help"] diff --git a/src/skill_seekers/cli/arguments/github.py b/src/skill_seekers/cli/arguments/github.py index f0e1b0f..0135420 100644 --- a/src/skill_seekers/cli/arguments/github.py +++ b/src/skill_seekers/cli/arguments/github.py @@ -5,12 +5,20 @@ Both github_scraper.py (standalone) and parsers/github_parser.py (unified CLI) import and use these definitions. This ensures the parsers NEVER drift out of sync. + +Shared arguments (name, description, output, enhance-level, api-key, +dry-run, verbose, quiet, workflow args) come from common.py / workflow.py +via ``add_all_standard_arguments()``. """ import argparse from typing import Any +from .common import add_all_standard_arguments + # GitHub-specific argument definitions as data structure +# NOTE: Shared args (name, description, enhance_level, api_key, dry_run, +# verbose, quiet, workflow args) are registered by add_all_standard_arguments(). GITHUB_ARGUMENTS: dict[str, dict[str, Any]] = { # Core GitHub options "repo": { @@ -37,22 +45,6 @@ GITHUB_ARGUMENTS: dict[str, dict[str, Any]] = { "metavar": "TOKEN", }, }, - "name": { - "flags": ("--name",), - "kwargs": { - "type": str, - "help": "Skill name (default: repo name)", - "metavar": "NAME", - }, - }, - "description": { - "flags": ("--description",), - "kwargs": { - "type": str, - "help": "Skill description", - "metavar": "TEXT", - }, - }, # Content options "no_issues": { "flags": ("--no-issues",), @@ -92,61 +84,6 @@ GITHUB_ARGUMENTS: dict[str, dict[str, Any]] = { "help": "Only scrape, don't build skill", }, }, - # Enhancement options - "enhance_level": { - "flags": ("--enhance-level",), - "kwargs": { - "type": int, - "choices": [0, 1, 2, 3], - "default": 2, - "help": ( - "AI enhancement level (auto-detects API vs LOCAL mode): " - "0=disabled, 1=SKILL.md only, 2=+architecture/config (default), 3=full enhancement. " - "Mode selection: uses API if ANTHROPIC_API_KEY is set, otherwise LOCAL (Claude Code)" - ), - "metavar": "LEVEL", - }, - }, - "api_key": { - "flags": ("--api-key",), - "kwargs": { - "type": str, - "help": "Anthropic API key for --enhance (or set ANTHROPIC_API_KEY)", - "metavar": "KEY", - }, - }, - # Enhancement Workflow arguments (NEW - Phase 2) - "enhance_workflow": { - "flags": ("--enhance-workflow",), - "kwargs": { - "action": "append", - "help": "Apply enhancement workflow (file path or preset: security-focus, minimal, api-documentation, architecture-comprehensive). Can use multiple times to chain workflows.", - "metavar": "WORKFLOW", - }, - }, - "enhance_stage": { - "flags": ("--enhance-stage",), - "kwargs": { - "action": "append", - "help": "Add inline enhancement stage ('name:prompt'). Can use multiple times.", - "metavar": "STAGE", - }, - }, - "var": { - "flags": ("--var",), - "kwargs": { - "action": "append", - "help": "Override workflow variable ('key=value'). Can use multiple times.", - "metavar": "VAR", - }, - }, - "workflow_dry_run": { - "flags": ("--workflow-dry-run",), - "kwargs": { - "action": "store_true", - "help": "Preview workflow without executing (requires --enhance-workflow)", - }, - }, # Mode options "non_interactive": { "flags": ("--non-interactive",), @@ -182,6 +119,10 @@ def add_github_arguments(parser: argparse.ArgumentParser) -> None: - github_scraper.py (standalone scraper) - parsers/github_parser.py (unified CLI) + Registers shared args (name, description, output, enhance-level, api-key, + dry-run, verbose, quiet, workflow args) via add_all_standard_arguments(), + then adds GitHub-specific args on top. + Args: parser: The ArgumentParser to add arguments to @@ -189,6 +130,10 @@ def add_github_arguments(parser: argparse.ArgumentParser) -> None: >>> parser = argparse.ArgumentParser() >>> add_github_arguments(parser) # Adds all github args """ + # Shared universal args first + add_all_standard_arguments(parser) + + # GitHub-specific args for arg_name, arg_def in GITHUB_ARGUMENTS.items(): flags = arg_def["flags"] kwargs = arg_def["kwargs"] @@ -199,9 +144,11 @@ def get_github_argument_names() -> set: """Get the set of github argument destination names. Returns: - Set of argument dest names + Set of argument dest names (includes shared + github-specific) """ - return set(GITHUB_ARGUMENTS.keys()) + from .common import get_all_standard_argument_names + + return get_all_standard_argument_names() | set(GITHUB_ARGUMENTS.keys()) def get_github_argument_count() -> int: @@ -210,4 +157,12 @@ def get_github_argument_count() -> int: Returns: Number of arguments """ - return len(GITHUB_ARGUMENTS) + from .common import COMMON_ARGUMENTS, BEHAVIOR_ARGUMENTS + from .workflow import WORKFLOW_ARGUMENTS + + return ( + len(GITHUB_ARGUMENTS) + + len(COMMON_ARGUMENTS) + + len(BEHAVIOR_ARGUMENTS) + + len(WORKFLOW_ARGUMENTS) + ) diff --git a/src/skill_seekers/cli/arguments/pdf.py b/src/skill_seekers/cli/arguments/pdf.py index 5f280dd..efd0542 100644 --- a/src/skill_seekers/cli/arguments/pdf.py +++ b/src/skill_seekers/cli/arguments/pdf.py @@ -3,11 +3,20 @@ This module defines ALL arguments for the pdf command in ONE place. Both pdf_scraper.py (standalone) and parsers/pdf_parser.py (unified CLI) import and use these definitions. + +Shared arguments (name, description, output, enhance-level, api-key, +dry-run, verbose, quiet, workflow args) come from common.py / workflow.py +via ``add_all_standard_arguments()``. """ import argparse from typing import Any +from .common import add_all_standard_arguments + +# PDF-specific argument definitions as data structure +# NOTE: Shared args (name, description, output, enhance_level, api_key, dry_run, +# verbose, quiet, workflow args) are registered by add_all_standard_arguments(). PDF_ARGUMENTS: dict[str, dict[str, Any]] = { "config": { "flags": ("--config",), @@ -25,22 +34,6 @@ PDF_ARGUMENTS: dict[str, dict[str, Any]] = { "metavar": "PATH", }, }, - "name": { - "flags": ("--name",), - "kwargs": { - "type": str, - "help": "Skill name (used with --pdf)", - "metavar": "NAME", - }, - }, - "description": { - "flags": ("--description",), - "kwargs": { - "type": str, - "help": "Skill description", - "metavar": "TEXT", - }, - }, "from_json": { "flags": ("--from-json",), "kwargs": { @@ -49,67 +42,32 @@ PDF_ARGUMENTS: dict[str, dict[str, Any]] = { "metavar": "FILE", }, }, - # Enhancement Workflow arguments (NEW - Phase 2) - "enhance_workflow": { - "flags": ("--enhance-workflow",), - "kwargs": { - "action": "append", - "help": "Apply enhancement workflow (file path or preset: security-focus, minimal, api-documentation, architecture-comprehensive). Can use multiple times to chain workflows.", - "metavar": "WORKFLOW", - }, - }, - "enhance_stage": { - "flags": ("--enhance-stage",), - "kwargs": { - "action": "append", - "help": "Add inline enhancement stage ('name:prompt'). Can use multiple times.", - "metavar": "STAGE", - }, - }, - "var": { - "flags": ("--var",), - "kwargs": { - "action": "append", - "help": "Override workflow variable ('key=value'). Can use multiple times.", - "metavar": "VAR", - }, - }, - "workflow_dry_run": { - "flags": ("--workflow-dry-run",), - "kwargs": { - "action": "store_true", - "help": "Preview workflow without executing (requires --enhance-workflow)", - }, - }, - # API key (parity with scrape/github/analyze) - "api_key": { - "flags": ("--api-key",), - "kwargs": { - "type": str, - "help": "Anthropic API key (or set ANTHROPIC_API_KEY env var)", - "metavar": "KEY", - }, - }, - # Enhancement level - "enhance_level": { - "flags": ("--enhance-level",), - "kwargs": { - "type": int, - "choices": [0, 1, 2, 3], - "default": 0, - "help": ( - "AI enhancement level (auto-detects API vs LOCAL mode): " - "0=disabled (default for PDF), 1=SKILL.md only, 2=+architecture/config, 3=full enhancement. " - "Mode selection: uses API if ANTHROPIC_API_KEY is set, otherwise LOCAL (Claude Code)" - ), - "metavar": "LEVEL", - }, - }, } def add_pdf_arguments(parser: argparse.ArgumentParser) -> None: - """Add all pdf command arguments to a parser.""" + """Add all pdf command arguments to a parser. + + Registers shared args (name, description, output, enhance-level, api-key, + dry-run, verbose, quiet, workflow args) via add_all_standard_arguments(), + then adds PDF-specific args on top. + + The default for --enhance-level is overridden to 0 (disabled) for PDF. + """ + # Shared universal args first + add_all_standard_arguments(parser) + + # Override enhance-level default to 0 for PDF + for action in parser._actions: + if hasattr(action, "dest") and action.dest == "enhance_level": + action.default = 0 + action.help = ( + "AI enhancement level (auto-detects API vs LOCAL mode): " + "0=disabled (default for PDF), 1=SKILL.md only, 2=+architecture/config, 3=full enhancement. " + "Mode selection: uses API if ANTHROPIC_API_KEY is set, otherwise LOCAL (Claude Code)" + ) + + # PDF-specific args for arg_name, arg_def in PDF_ARGUMENTS.items(): flags = arg_def["flags"] kwargs = arg_def["kwargs"] diff --git a/src/skill_seekers/cli/arguments/scrape.py b/src/skill_seekers/cli/arguments/scrape.py index f9531ef..63b5781 100644 --- a/src/skill_seekers/cli/arguments/scrape.py +++ b/src/skill_seekers/cli/arguments/scrape.py @@ -5,16 +5,21 @@ Both doc_scraper.py (standalone) and parsers/scrape_parser.py (unified CLI) import and use these definitions. This ensures the parsers NEVER drift out of sync. + +Shared arguments (name, description, output, enhance-level, api-key, +dry-run, verbose, quiet, workflow args) come from common.py / workflow.py +via ``add_all_standard_arguments()``. """ import argparse from typing import Any from skill_seekers.cli.constants import DEFAULT_RATE_LIMIT -from .common import RAG_ARGUMENTS +from .common import add_all_standard_arguments, RAG_ARGUMENTS # Scrape-specific argument definitions as data structure -# This enables introspection for UI generation and testing +# NOTE: Shared args (name, description, enhance_level, api_key, dry_run, +# verbose, quiet, workflow args) are registered by add_all_standard_arguments(). SCRAPE_ARGUMENTS: dict[str, dict[str, Any]] = { # Positional argument "url_positional": { @@ -25,7 +30,7 @@ SCRAPE_ARGUMENTS: dict[str, dict[str, Any]] = { "help": "Base documentation URL (alternative to --url)", }, }, - # Common arguments (also defined in common.py for other commands) + # Config file (scrape-specific — loads selectors, categories, etc.) "config": { "flags": ("--config", "-c"), "kwargs": { @@ -34,77 +39,6 @@ SCRAPE_ARGUMENTS: dict[str, dict[str, Any]] = { "metavar": "FILE", }, }, - "name": { - "flags": ("--name",), - "kwargs": { - "type": str, - "help": "Skill name (used for output directory and filenames)", - "metavar": "NAME", - }, - }, - "description": { - "flags": ("--description", "-d"), - "kwargs": { - "type": str, - "help": "Skill description (used in SKILL.md)", - "metavar": "TEXT", - }, - }, - # Enhancement arguments - "enhance_level": { - "flags": ("--enhance-level",), - "kwargs": { - "type": int, - "choices": [0, 1, 2, 3], - "default": 2, - "help": ( - "AI enhancement level (auto-detects API vs LOCAL mode): " - "0=disabled, 1=SKILL.md only, 2=+architecture/config (default), 3=full enhancement. " - "Mode selection: uses API if ANTHROPIC_API_KEY is set, otherwise LOCAL (Claude Code)" - ), - "metavar": "LEVEL", - }, - }, - "api_key": { - "flags": ("--api-key",), - "kwargs": { - "type": str, - "help": "Anthropic API key for --enhance (or set ANTHROPIC_API_KEY env var)", - "metavar": "KEY", - }, - }, - # Enhancement Workflow arguments (NEW - Phase 2) - "enhance_workflow": { - "flags": ("--enhance-workflow",), - "kwargs": { - "action": "append", - "help": "Apply enhancement workflow (file path or preset: security-focus, minimal, api-documentation, architecture-comprehensive). Can use multiple times to chain workflows.", - "metavar": "WORKFLOW", - }, - }, - "enhance_stage": { - "flags": ("--enhance-stage",), - "kwargs": { - "action": "append", - "help": "Add inline enhancement stage ('name:prompt'). Can use multiple times.", - "metavar": "STAGE", - }, - }, - "var": { - "flags": ("--var",), - "kwargs": { - "action": "append", - "help": "Override workflow variable ('key=value'). Can use multiple times.", - "metavar": "VAR", - }, - }, - "workflow_dry_run": { - "flags": ("--workflow-dry-run",), - "kwargs": { - "action": "store_true", - "help": "Preview workflow without executing (requires --enhance-workflow)", - }, - }, # Scrape-specific options "interactive": { "flags": ("--interactive", "-i"), @@ -136,13 +70,6 @@ SCRAPE_ARGUMENTS: dict[str, dict[str, Any]] = { "help": "Skip scraping, use existing data", }, }, - "dry_run": { - "flags": ("--dry-run",), - "kwargs": { - "action": "store_true", - "help": "Preview what will be scraped without actually scraping", - }, - }, "resume": { "flags": ("--resume",), "kwargs": { @@ -195,20 +122,6 @@ SCRAPE_ARGUMENTS: dict[str, dict[str, Any]] = { "help": "Open terminal window for enhancement (use with --enhance-local)", }, }, - "verbose": { - "flags": ("--verbose", "-v"), - "kwargs": { - "action": "store_true", - "help": "Enable verbose output (DEBUG level logging)", - }, - }, - "quiet": { - "flags": ("--quiet", "-q"), - "kwargs": { - "action": "store_true", - "help": "Minimize output (WARNING level logging only)", - }, - }, # RAG chunking options (imported from common.py - see RAG_ARGUMENTS) # Note: RAG arguments will be merged at runtime "no_preserve_code_blocks": { @@ -239,13 +152,21 @@ def add_scrape_arguments(parser: argparse.ArgumentParser) -> None: - doc_scraper.py (standalone scraper) - parsers/scrape_parser.py (unified CLI) + Registers shared args (name, description, output, enhance-level, api-key, + dry-run, verbose, quiet, workflow args) via add_all_standard_arguments(), + then adds scrape-specific args on top. + Args: parser: The ArgumentParser to add arguments to Example: >>> parser = argparse.ArgumentParser() - >>> add_scrape_arguments(parser) # Adds all 26 scrape args + >>> add_scrape_arguments(parser) """ + # Shared universal args first + add_all_standard_arguments(parser) + + # Scrape-specific args for arg_name, arg_def in SCRAPE_ARGUMENTS.items(): flags = arg_def["flags"] kwargs = arg_def["kwargs"] @@ -256,9 +177,11 @@ def get_scrape_argument_names() -> set: """Get the set of scrape argument destination names. Returns: - Set of argument dest names + Set of argument dest names (includes shared + scrape-specific) """ - return set(SCRAPE_ARGUMENTS.keys()) + from .common import get_all_standard_argument_names + + return get_all_standard_argument_names() | set(SCRAPE_ARGUMENTS.keys()) def get_scrape_argument_count() -> int: @@ -267,4 +190,12 @@ def get_scrape_argument_count() -> int: Returns: Number of arguments """ - return len(SCRAPE_ARGUMENTS) + from .common import COMMON_ARGUMENTS, BEHAVIOR_ARGUMENTS + from .workflow import WORKFLOW_ARGUMENTS + + return ( + len(SCRAPE_ARGUMENTS) + + len(COMMON_ARGUMENTS) + + len(BEHAVIOR_ARGUMENTS) + + len(WORKFLOW_ARGUMENTS) + ) diff --git a/src/skill_seekers/cli/codebase_scraper.py b/src/skill_seekers/cli/codebase_scraper.py index 3736a14..fc27740 100644 --- a/src/skill_seekers/cli/codebase_scraper.py +++ b/src/skill_seekers/cli/codebase_scraper.py @@ -1056,6 +1056,8 @@ def analyze_codebase( extract_config_patterns: bool = True, extract_docs: bool = True, enhance_level: int = 0, + skill_name: str | None = None, + skill_description: str | None = None, ) -> dict[str, Any]: """ Analyze local codebase and extract code knowledge. @@ -1075,6 +1077,8 @@ def analyze_codebase( extract_config_patterns: Extract configuration patterns from config files (C3.4) extract_docs: Extract and process markdown documentation files (default: True) enhance_level: AI enhancement level (0=off, 1=SKILL.md only, 2=+config+arch+docs, 3=full) + skill_name: Optional override for skill name (default: directory name) + skill_description: Optional override for skill description Returns: Analysis results dictionary @@ -1598,6 +1602,8 @@ def analyze_codebase( extract_config_patterns=extract_config_patterns, extract_docs=extract_docs, docs_data=docs_data, + skill_name=skill_name, + skill_description=skill_description, ) return results @@ -1615,6 +1621,8 @@ def _generate_skill_md( extract_config_patterns: bool, extract_docs: bool = True, docs_data: dict[str, Any] | None = None, + skill_name: str | None = None, + skill_description: str | None = None, ): """ Generate rich SKILL.md from codebase analysis results. @@ -1633,10 +1641,14 @@ def _generate_skill_md( repo_name = directory.name # Generate skill name (lowercase, hyphens only, max 64 chars) - skill_name = repo_name.lower().replace("_", "-").replace(" ", "-")[:64] + # Use CLI override if provided, otherwise derive from directory name + if skill_name: + skill_name = skill_name.lower().replace("_", "-").replace(" ", "-")[:64] + else: + skill_name = repo_name.lower().replace("_", "-").replace(" ", "-")[:64] - # Generate description - description = f"Local codebase analysis for {repo_name}" + # Generate description (use CLI override if provided) + description = skill_description or f"Local codebase analysis for {repo_name}" # Count files by language language_stats = _get_language_stats(results.get("files", [])) @@ -2257,6 +2269,8 @@ def _check_deprecated_flags(args): def main(): """Command-line interface for codebase analysis.""" + from skill_seekers.cli.arguments.analyze import add_analyze_arguments + parser = argparse.ArgumentParser( description="Analyze local codebases and extract code knowledge", formatter_class=argparse.RawDescriptionHelpFormatter, @@ -2285,92 +2299,10 @@ Examples: """, ) - parser.add_argument("--directory", required=True, help="Directory to analyze") - parser.add_argument( - "--output", default="output/codebase/", help="Output directory (default: output/codebase/)" - ) + # Register all args from the shared definitions module + add_analyze_arguments(parser) - # Preset selection (NEW - recommended way) - parser.add_argument( - "--preset", - choices=["quick", "standard", "comprehensive"], - help="Analysis preset: quick (1-2 min), standard (5-10 min, DEFAULT), comprehensive (20-60 min)", - ) - parser.add_argument( - "--preset-list", action="store_true", help="Show available presets and exit" - ) - - # Legacy preset flags (kept for backward compatibility) - parser.add_argument( - "--quick", - action="store_true", - help="[DEPRECATED] Quick analysis - use '--preset quick' instead", - ) - parser.add_argument( - "--comprehensive", - action="store_true", - help="[DEPRECATED] Comprehensive analysis - use '--preset comprehensive' instead", - ) - - parser.add_argument( - "--depth", - choices=["surface", "deep", "full"], - default=None, # Don't set default here - let preset system handle it - help=( - "[DEPRECATED] Analysis depth - use --preset instead. " - "surface (basic code structure, ~1-2 min), " - "deep (code + patterns + tests, ~5-10 min, DEFAULT), " - "full (everything + AI enhancement, ~20-60 min)" - ), - ) - parser.add_argument( - "--languages", help="Comma-separated languages to analyze (e.g., Python,JavaScript,C++)" - ) - parser.add_argument( - "--file-patterns", help="Comma-separated file patterns (e.g., *.py,src/**/*.js)" - ) - parser.add_argument( - "--skip-api-reference", - action="store_true", - default=False, - help="Skip API reference markdown documentation generation (default: enabled)", - ) - parser.add_argument( - "--skip-dependency-graph", - action="store_true", - default=False, - help="Skip dependency graph and circular dependency detection (default: enabled)", - ) - parser.add_argument( - "--skip-patterns", - action="store_true", - default=False, - help="Skip design pattern detection (Singleton, Factory, Observer, etc.) (default: enabled)", - ) - parser.add_argument( - "--skip-test-examples", - action="store_true", - default=False, - help="Skip test example extraction (instantiation, method calls, configs, etc.) (default: enabled)", - ) - parser.add_argument( - "--skip-how-to-guides", - action="store_true", - default=False, - help="Skip how-to guide generation from workflow examples (default: enabled)", - ) - parser.add_argument( - "--skip-config-patterns", - action="store_true", - default=False, - help="Skip configuration pattern extraction from config files (JSON, YAML, TOML, ENV, etc.) (default: enabled)", - ) - parser.add_argument( - "--skip-docs", - action="store_true", - default=False, - help="Skip project documentation extraction from markdown files (README, docs/, etc.) (default: enabled)", - ) + # Extra legacy arg only used by standalone CLI (not in arguments/analyze.py) parser.add_argument( "--ai-mode", choices=["auto", "api", "local", "none"], @@ -2384,61 +2316,6 @@ Examples: "💡 TIP: Use --enhance flag instead for simpler UX!" ), ) - parser.add_argument("--no-comments", action="store_true", help="Skip comment extraction") - parser.add_argument("--verbose", action="store_true", help="Enable verbose logging") - parser.add_argument( - "--enhance-level", - type=int, - choices=[0, 1, 2, 3], - default=0, - help=( - "AI enhancement level: " - "0=off (default), " - "1=SKILL.md only, " - "2=SKILL.md+Architecture+Config, " - "3=full (patterns, tests, config, architecture, SKILL.md)" - ), - ) - - # Workflow enhancement arguments - parser.add_argument( - "--enhance-workflow", - action="append", - help=( - "Enhancement workflow to use (name or path to YAML file). " - "Can be used multiple times to chain workflows. " - "Examples: 'security-focus', 'architecture-comprehensive', " - "'.skill-seekers/my-workflow.yaml'. " - "Overrides --enhance-level when provided." - ), - metavar="WORKFLOW", - ) - parser.add_argument( - "--enhance-stage", - type=str, - action="append", - help=( - "Add inline enhancement stage. Format: 'name:prompt'. " - "Can be used multiple times. Example: " - "--enhance-stage 'security:Analyze for security issues'" - ), - metavar="NAME:PROMPT", - ) - parser.add_argument( - "--var", - type=str, - action="append", - help=( - "Override workflow variable. Format: 'key=value'. " - "Can be used multiple times. Example: --var focus_area=performance" - ), - metavar="KEY=VALUE", - ) - parser.add_argument( - "--workflow-dry-run", - action="store_true", - help="Show workflow stages without executing (dry run mode)", - ) # Check for deprecated flags deprecated_flags = { @@ -2506,9 +2383,40 @@ Examples: args.depth = "deep" # Default depth # Set logging level - if args.verbose: + if getattr(args, "quiet", False): + logging.getLogger().setLevel(logging.WARNING) + elif args.verbose: logging.getLogger().setLevel(logging.DEBUG) + # Handle --dry-run + if getattr(args, "dry_run", False): + directory = Path(args.directory) + print(f"\n{'=' * 60}") + print(f"DRY RUN: Codebase Analysis") + print(f"{'=' * 60}") + print(f"Directory: {directory.resolve()}") + print(f"Output: {args.output}") + print(f"Preset: {preset_name}") + print(f"Depth: {args.depth or 'deep (default)'}") + print(f"Name: {getattr(args, 'name', None) or directory.name}") + print(f"Enhance: level {args.enhance_level}") + print(f"Skip flags: ", end="") + skips = [] + for flag in [ + "skip_api_reference", + "skip_dependency_graph", + "skip_patterns", + "skip_test_examples", + "skip_how_to_guides", + "skip_config_patterns", + "skip_docs", + ]: + if getattr(args, flag, False): + skips.append(f"--{flag.replace('_', '-')}") + print(", ".join(skips) if skips else "(none)") + print(f"\n✅ Dry run complete") + return 0 + # Validate directory directory = Path(args.directory) if not directory.exists(): @@ -2546,6 +2454,8 @@ Examples: extract_config_patterns=not args.skip_config_patterns, extract_docs=not args.skip_docs, enhance_level=args.enhance_level, # AI enhancement level (0-3) + skill_name=getattr(args, "name", None), + skill_description=getattr(args, "description", None), ) # ============================================================ diff --git a/src/skill_seekers/cli/create_command.py b/src/skill_seekers/cli/create_command.py index 717039d..0dd69f4 100644 --- a/src/skill_seekers/cli/create_command.py +++ b/src/skill_seekers/cli/create_command.py @@ -151,7 +151,27 @@ class CreateCommand: # Add universal arguments self._add_common_args(argv) - # Add web-specific arguments + # Config file (web-specific — loads selectors, categories, etc.) + if self.args.config: + argv.extend(["--config", self.args.config]) + + # RAG arguments (web scraper only) + if getattr(self.args, "chunk_for_rag", False): + argv.append("--chunk-for-rag") + if getattr(self.args, "chunk_size", None) and self.args.chunk_size != 512: + argv.extend(["--chunk-size", str(self.args.chunk_size)]) + if getattr(self.args, "chunk_overlap", None) and self.args.chunk_overlap != 50: + argv.extend(["--chunk-overlap", str(self.args.chunk_overlap)]) + + # Advanced web-specific arguments + if getattr(self.args, "no_preserve_code_blocks", False): + argv.append("--no-preserve-code-blocks") + if getattr(self.args, "no_preserve_paragraphs", False): + argv.append("--no-preserve-paragraphs") + if getattr(self.args, "interactive_enhancement", False): + argv.append("--interactive-enhancement") + + # Web-specific arguments if getattr(self.args, "max_pages", None): argv.extend(["--max-pages", str(self.args.max_pages)]) if getattr(self.args, "skip_scrape", False): @@ -192,6 +212,10 @@ class CreateCommand: # Add universal arguments self._add_common_args(argv) + # Config file (github-specific) + if self.args.config: + argv.extend(["--config", self.args.config]) + # Add GitHub-specific arguments if getattr(self.args, "token", None): argv.extend(["--token", self.args.token]) @@ -235,6 +259,10 @@ class CreateCommand: # Add universal arguments self._add_common_args(argv) + # Preset (local codebase scraper has preset support) + if getattr(self.args, "preset", None): + argv.extend(["--preset", self.args.preset]) + # Add local-specific arguments if getattr(self.args, "languages", None): argv.extend(["--languages", self.args.languages]) @@ -336,10 +364,15 @@ class CreateCommand: sys.argv = original_argv def _add_common_args(self, argv: list[str]) -> None: - """Add common/universal arguments to argv list. + """Add truly universal arguments to argv list. - Args: - argv: Argument list to append to + These flags are accepted by ALL scrapers (doc, github, codebase, pdf) + because each scraper calls ``add_all_standard_arguments(parser)`` + which registers: name, description, output, enhance-level, api-key, + dry-run, verbose, quiet, and workflow args. + + Route-specific flags (preset, config, RAG, preserve, etc.) are + forwarded only by the _route_*() method that needs them. """ # Identity arguments if self.args.name: @@ -367,31 +400,7 @@ class CreateCommand: if self.args.quiet: argv.append("--quiet") - # RAG arguments (NEW - universal!) - if getattr(self.args, "chunk_for_rag", False): - argv.append("--chunk-for-rag") - if getattr(self.args, "chunk_size", None) and self.args.chunk_size != 512: - argv.extend(["--chunk-size", str(self.args.chunk_size)]) - if getattr(self.args, "chunk_overlap", None) and self.args.chunk_overlap != 50: - argv.extend(["--chunk-overlap", str(self.args.chunk_overlap)]) - - # Preset argument - if getattr(self.args, "preset", None): - argv.extend(["--preset", self.args.preset]) - - # Config file - if self.args.config: - argv.extend(["--config", self.args.config]) - - # Advanced arguments - if getattr(self.args, "no_preserve_code_blocks", False): - argv.append("--no-preserve-code-blocks") - if getattr(self.args, "no_preserve_paragraphs", False): - argv.append("--no-preserve-paragraphs") - if getattr(self.args, "interactive_enhancement", False): - argv.append("--interactive-enhancement") - - # Enhancement Workflow arguments (NEW - Phase 2) + # Enhancement Workflow arguments if getattr(self.args, "enhance_workflow", None): for wf in self.args.enhance_workflow: argv.extend(["--enhance-workflow", wf]) diff --git a/src/skill_seekers/cli/github_scraper.py b/src/skill_seekers/cli/github_scraper.py index cc554e3..e742280 100644 --- a/src/skill_seekers/cli/github_scraper.py +++ b/src/skill_seekers/cli/github_scraper.py @@ -1391,6 +1391,29 @@ def main(): parser = setup_argument_parser() args = parser.parse_args() + # Set logging level from behavior args + if getattr(args, "quiet", False): + logging.getLogger().setLevel(logging.WARNING) + elif getattr(args, "verbose", False): + logging.getLogger().setLevel(logging.DEBUG) + + # Handle --dry-run + if getattr(args, "dry_run", False): + repo = args.repo or (args.config and "(from config)") + print(f"\n{'=' * 60}") + print(f"DRY RUN: GitHub Repository Analysis") + print(f"{'=' * 60}") + print(f"Repository: {repo}") + print(f"Name: {getattr(args, 'name', None) or '(auto-detect)'}") + print(f"Include issues: {not getattr(args, 'no_issues', False)}") + print(f"Include releases: {not getattr(args, 'no_releases', False)}") + print(f"Include changelog: {not getattr(args, 'no_changelog', False)}") + print(f"Max issues: {getattr(args, 'max_issues', 100)}") + print(f"Enhance level: {getattr(args, 'enhance_level', 0)}") + print(f"Profile: {getattr(args, 'profile', None) or '(default)'}") + print(f"\n✅ Dry run complete") + return 0 + # Build config from args or file if args.config: with open(args.config, encoding="utf-8") as f: diff --git a/src/skill_seekers/cli/main.py b/src/skill_seekers/cli/main.py index 657088e..7c9bf80 100644 --- a/src/skill_seekers/cli/main.py +++ b/src/skill_seekers/cli/main.py @@ -305,6 +305,30 @@ def _handle_analyze_command(args: argparse.Namespace) -> int: sys.argv.append("--no-comments") if args.verbose: sys.argv.append("--verbose") + if getattr(args, "quiet", False): + sys.argv.append("--quiet") + if getattr(args, "dry_run", False): + sys.argv.append("--dry-run") + if getattr(args, "preset", None): + sys.argv.extend(["--preset", args.preset]) + if getattr(args, "name", None): + sys.argv.extend(["--name", args.name]) + if getattr(args, "description", None): + sys.argv.extend(["--description", args.description]) + if getattr(args, "api_key", None): + sys.argv.extend(["--api-key", args.api_key]) + # Enhancement Workflow arguments + if getattr(args, "enhance_workflow", None): + for wf in args.enhance_workflow: + sys.argv.extend(["--enhance-workflow", wf]) + if getattr(args, "enhance_stage", None): + for stage in args.enhance_stage: + sys.argv.extend(["--enhance-stage", stage]) + if getattr(args, "workflow_var", None): + for var in args.workflow_var: + sys.argv.extend(["--var", var]) + if getattr(args, "workflow_dry_run", False): + sys.argv.append("--workflow-dry-run") try: result = analyze_main() or 0 diff --git a/src/skill_seekers/cli/pdf_scraper.py b/src/skill_seekers/cli/pdf_scraper.py index cda7096..9ffd60f 100644 --- a/src/skill_seekers/cli/pdf_scraper.py +++ b/src/skill_seekers/cli/pdf_scraper.py @@ -13,6 +13,7 @@ Usage: import argparse import json +import logging import os import re import sys @@ -644,6 +645,24 @@ def main(): args = parser.parse_args() + # Set logging level from behavior args + if getattr(args, "quiet", False): + logging.getLogger().setLevel(logging.WARNING) + elif getattr(args, "verbose", False): + logging.getLogger().setLevel(logging.DEBUG) + + # Handle --dry-run + if getattr(args, "dry_run", False): + source = args.pdf or args.config or args.from_json or "(none)" + print(f"\n{'=' * 60}") + print(f"DRY RUN: PDF Extraction") + print(f"{'=' * 60}") + print(f"Source: {source}") + print(f"Name: {getattr(args, 'name', None) or '(auto-detect)'}") + print(f"Enhance level: {getattr(args, 'enhance_level', 0)}") + print(f"\n✅ Dry run complete") + return + # Validate inputs if not (args.config or args.pdf or args.from_json): parser.error("Must specify --config, --pdf, or --from-json") From 5ae57d192ac659aacdd3c27b6d0c440248590df5 Mon Sep 17 00:00:00 2001 From: yusyus Date: Tue, 24 Feb 2026 06:52:55 +0300 Subject: [PATCH 2/4] fix: update Gemini model to 2.5-flash and add API auto-detection in enhance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix 1 — gemini.py: replace deprecated gemini-2.0-flash-exp (404 errors) with gemini-2.5-flash (stable, GA, Google's recommended replacement). Closes #290. Fix 2 — enhance dispatcher: implement the documented auto-detection that was missing from the code. skill-seekers enhance now correctly routes: - ANTHROPIC_API_KEY set → Claude API mode (enhance_skill.py) - GOOGLE_API_KEY set → Gemini API mode - OPENAI_API_KEY set → OpenAI API mode - No API keys → LOCAL mode (Claude Code Max, free) Use --mode LOCAL to force local mode even when an API key is present. 9 new tests cover _detect_api_target() priority logic and main() routing (API delegation, --mode LOCAL override, no-key fallback). Co-Authored-By: Claude Sonnet 4.6 --- src/skill_seekers/cli/adaptors/gemini.py | 10 +- src/skill_seekers/cli/enhance_skill_local.py | 111 ++++++++++++-- tests/test_enhance_skill_local.py | 145 +++++++++++++++++++ 3 files changed, 246 insertions(+), 20 deletions(-) diff --git a/src/skill_seekers/cli/adaptors/gemini.py b/src/skill_seekers/cli/adaptors/gemini.py index af74a8a..3e58f1b 100644 --- a/src/skill_seekers/cli/adaptors/gemini.py +++ b/src/skill_seekers/cli/adaptors/gemini.py @@ -3,7 +3,7 @@ Google Gemini Adaptor Implements platform-specific handling for Google Gemini skills. -Uses Gemini Files API for grounding and Gemini 2.0 Flash for enhancement. +Uses Gemini Files API for grounding and Gemini 2.5 Flash for enhancement. """ import json @@ -23,7 +23,7 @@ class GeminiAdaptor(SkillAdaptor): - Plain markdown format (no YAML frontmatter) - tar.gz packaging for Gemini Files API - Upload to Google AI Studio / Files API - - AI enhancement using Gemini 2.0 Flash + - AI enhancement using Gemini 2.5 Flash """ PLATFORM = "gemini" @@ -279,7 +279,7 @@ See the references directory for complete documentation with examples and best p def supports_enhancement(self) -> bool: """ - Gemini supports AI enhancement via Gemini 2.0 Flash. + Gemini supports AI enhancement via Gemini 2.5 Flash. Returns: True @@ -288,7 +288,7 @@ See the references directory for complete documentation with examples and best p def enhance(self, skill_dir: Path, api_key: str) -> bool: """ - Enhance SKILL.md using Gemini 2.0 Flash API. + Enhance SKILL.md using Gemini 2.5 Flash API. Args: skill_dir: Path to skill directory @@ -338,7 +338,7 @@ See the references directory for complete documentation with examples and best p try: genai.configure(api_key=api_key) - model = genai.GenerativeModel("gemini-2.0-flash-exp") + model = genai.GenerativeModel("gemini-2.5-flash") response = model.generate_content(prompt) diff --git a/src/skill_seekers/cli/enhance_skill_local.py b/src/skill_seekers/cli/enhance_skill_local.py index a69f837..1ddc59f 100644 --- a/src/skill_seekers/cli/enhance_skill_local.py +++ b/src/skill_seekers/cli/enhance_skill_local.py @@ -1205,46 +1205,119 @@ except Exception as e: return False +def _detect_api_target() -> tuple[str, str] | None: + """ + Auto-detect which API platform to use for enhancement based on env vars. + + Priority: ANTHROPIC_API_KEY > GOOGLE_API_KEY > OPENAI_API_KEY + + Returns: + (target, api_key) tuple if an API key is found, else None. + """ + anthropic_key = os.environ.get("ANTHROPIC_API_KEY") or os.environ.get( + "ANTHROPIC_AUTH_TOKEN" + ) + if anthropic_key: + return ("claude", anthropic_key) + + google_key = os.environ.get("GOOGLE_API_KEY") + if google_key: + return ("gemini", google_key) + + openai_key = os.environ.get("OPENAI_API_KEY") + if openai_key: + return ("openai", openai_key) + + return None + + +def _run_api_enhance(target: str, api_key: str) -> None: + """Delegate to enhance_skill.main() for API-mode enhancement.""" + import sys + + from skill_seekers.cli.enhance_skill import main as api_main + + # Find the skill_directory positional arg (first non-flag arg after argv[0]) + skill_dir = None + dry_run = False + i = 1 + while i < len(sys.argv): + arg = sys.argv[i] + if arg == "--dry-run": + dry_run = True + elif arg in ("--mode",): + i += 1 # skip value + elif not arg.startswith("-") and skill_dir is None: + skill_dir = arg + i += 1 + + if not skill_dir: + print("❌ Error: skill_directory is required") + sys.exit(1) + + new_argv = [sys.argv[0], skill_dir, "--target", target, "--api-key", api_key] + if dry_run: + new_argv.append("--dry-run") + sys.argv = new_argv + api_main() + + def main(): import argparse parser = argparse.ArgumentParser( - description="Enhance a skill with a local coding agent (no API key)", + description="Enhance a skill using AI (auto-detects API or local agent mode)", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" +Auto-detection (no flags needed): + If ANTHROPIC_API_KEY is set → Claude API mode + If GOOGLE_API_KEY is set → Gemini API mode + If OPENAI_API_KEY is set → OpenAI API mode + Otherwise → LOCAL mode (Claude Code Max, free) + Examples: - # Headless mode (default - runs in foreground, waits for completion, auto-force) + # Auto-detect mode based on env vars (recommended) skill-seekers enhance output/react/ - # Background mode (runs in background, returns immediately) - skill-seekers enhance output/react/ --background + # Force LOCAL mode even if API keys are set + skill-seekers enhance output/react/ --mode LOCAL - # Daemon mode (persistent background process, fully detached) - skill-seekers enhance output/react/ --daemon + # LOCAL: background mode (runs in background, returns immediately) + skill-seekers enhance output/react/ --mode LOCAL --background - # Disable force mode (ask for confirmations) - skill-seekers enhance output/react/ --no-force + # LOCAL: daemon mode (persistent background process, fully detached) + skill-seekers enhance output/react/ --mode LOCAL --daemon - # Interactive mode (opens terminal window) - skill-seekers enhance output/react/ --interactive-enhancement + # LOCAL: interactive mode (opens terminal window) + skill-seekers enhance output/react/ --mode LOCAL --interactive-enhancement - # Custom timeout - skill-seekers enhance output/react/ --timeout 1200 + # LOCAL: custom timeout + skill-seekers enhance output/react/ --mode LOCAL --timeout 1200 -Mode Comparison: +LOCAL Mode Comparison: - headless: Runs local agent CLI directly, BLOCKS until done (default) - background: Runs in background thread, returns immediately - daemon: Fully detached process, continues after parent exits - terminal: Opens new terminal window (interactive) -Force Mode (Default ON): - By default, all modes skip confirmations (auto-yes). +Force Mode (LOCAL only, Default ON): + By default, all LOCAL modes skip confirmations (auto-yes). Use --no-force to enable confirmation prompts. """, ) parser.add_argument("skill_directory", help="Path to skill directory (e.g., output/react/)") + parser.add_argument( + "--mode", + choices=["LOCAL", "API"], + help=( + "Force enhancement mode. LOCAL uses a local coding agent (free). " + "API uses the platform API (requires API key). " + "Default: auto-detect from environment variables." + ), + ) + parser.add_argument( "--agent", choices=sorted(list(AGENT_PRESETS.keys()) + ["custom"]), @@ -1290,6 +1363,14 @@ Force Mode (Default ON): args = parser.parse_args() + # Auto-detect API mode unless --mode LOCAL is explicitly set + if getattr(args, "mode", None) != "LOCAL": + api_target = _detect_api_target() + if api_target is not None: + target, api_key = api_target + _run_api_enhance(target, api_key) + return + # Validate mutually exclusive options mode_count = sum([args.interactive_enhancement, args.background, args.daemon]) if mode_count > 1: diff --git a/tests/test_enhance_skill_local.py b/tests/test_enhance_skill_local.py index 557dd25..601db7e 100644 --- a/tests/test_enhance_skill_local.py +++ b/tests/test_enhance_skill_local.py @@ -596,3 +596,148 @@ class TestRunBackground: # Should have returned quickly (not waited for the slow thread) assert result is True assert elapsed < 0.4, f"_run_background took {elapsed:.2f}s - should return immediately" + + +class TestEnhanceDispatcher: + """Test auto-detection of API vs LOCAL mode in enhance main().""" + + def test_detect_api_target_anthropic(self, monkeypatch): + """ANTHROPIC_API_KEY detected as claude target.""" + from skill_seekers.cli.enhance_skill_local import _detect_api_target + + monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-test") + monkeypatch.delenv("GOOGLE_API_KEY", raising=False) + monkeypatch.delenv("OPENAI_API_KEY", raising=False) + result = _detect_api_target() + assert result == ("claude", "sk-ant-test") + + def test_detect_api_target_google(self, monkeypatch): + """GOOGLE_API_KEY detected as gemini target when no Anthropic key.""" + from skill_seekers.cli.enhance_skill_local import _detect_api_target + + monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False) + monkeypatch.delenv("ANTHROPIC_AUTH_TOKEN", raising=False) + monkeypatch.setenv("GOOGLE_API_KEY", "AIza-test") + monkeypatch.delenv("OPENAI_API_KEY", raising=False) + result = _detect_api_target() + assert result == ("gemini", "AIza-test") + + def test_detect_api_target_openai(self, monkeypatch): + """OPENAI_API_KEY detected as openai target when no higher-priority key.""" + from skill_seekers.cli.enhance_skill_local import _detect_api_target + + monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False) + monkeypatch.delenv("ANTHROPIC_AUTH_TOKEN", raising=False) + monkeypatch.delenv("GOOGLE_API_KEY", raising=False) + monkeypatch.setenv("OPENAI_API_KEY", "sk-openai-test") + result = _detect_api_target() + assert result == ("openai", "sk-openai-test") + + def test_detect_api_target_none(self, monkeypatch): + """Returns None when no API keys are set.""" + from skill_seekers.cli.enhance_skill_local import _detect_api_target + + monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False) + monkeypatch.delenv("ANTHROPIC_AUTH_TOKEN", raising=False) + monkeypatch.delenv("GOOGLE_API_KEY", raising=False) + monkeypatch.delenv("OPENAI_API_KEY", raising=False) + result = _detect_api_target() + assert result is None + + def test_detect_api_target_anthropic_priority(self, monkeypatch): + """ANTHROPIC_API_KEY takes priority over GOOGLE_API_KEY.""" + from skill_seekers.cli.enhance_skill_local import _detect_api_target + + monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-test") + monkeypatch.setenv("GOOGLE_API_KEY", "AIza-test") + monkeypatch.setenv("OPENAI_API_KEY", "sk-openai-test") + result = _detect_api_target() + assert result == ("claude", "sk-ant-test") + + def test_detect_api_target_auth_token_fallback(self, monkeypatch): + """ANTHROPIC_AUTH_TOKEN is used when ANTHROPIC_API_KEY is absent.""" + from skill_seekers.cli.enhance_skill_local import _detect_api_target + + monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False) + monkeypatch.setenv("ANTHROPIC_AUTH_TOKEN", "sk-auth-test") + monkeypatch.delenv("GOOGLE_API_KEY", raising=False) + monkeypatch.delenv("OPENAI_API_KEY", raising=False) + result = _detect_api_target() + assert result == ("claude", "sk-auth-test") + + def test_main_delegates_to_api_when_key_set(self, monkeypatch, tmp_path): + """main() calls _run_api_enhance when an API key is detected.""" + import sys + from skill_seekers.cli.enhance_skill_local import main + + skill_dir = _make_skill_dir(tmp_path) + monkeypatch.setenv("GOOGLE_API_KEY", "AIza-test") + monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False) + monkeypatch.delenv("ANTHROPIC_AUTH_TOKEN", raising=False) + monkeypatch.delenv("OPENAI_API_KEY", raising=False) + monkeypatch.setattr(sys, "argv", ["enhance", str(skill_dir)]) + + called_with = {} + + def fake_run_api(target, api_key): + called_with["target"] = target + called_with["api_key"] = api_key + + monkeypatch.setattr( + "skill_seekers.cli.enhance_skill_local._run_api_enhance", fake_run_api + ) + main() + assert called_with == {"target": "gemini", "api_key": "AIza-test"} + + def test_main_uses_local_when_mode_local(self, monkeypatch, tmp_path): + """main() stays in LOCAL mode when --mode LOCAL is passed.""" + import sys + from skill_seekers.cli.enhance_skill_local import main + + skill_dir = _make_skill_dir(tmp_path) + monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-test") + monkeypatch.setattr(sys, "argv", ["enhance", str(skill_dir), "--mode", "LOCAL"]) + + api_called = [] + monkeypatch.setattr( + "skill_seekers.cli.enhance_skill_local._run_api_enhance", + lambda *a: api_called.append(a), + ) + + # LocalSkillEnhancer.run will fail without a real agent, just verify + # _run_api_enhance was NOT called + with patch("skill_seekers.cli.enhance_skill_local.LocalSkillEnhancer") as mock_enhancer: + mock_instance = MagicMock() + mock_instance.run.return_value = True + mock_enhancer.return_value = mock_instance + with pytest.raises(SystemExit): + main() + + assert api_called == [], "_run_api_enhance should not be called in LOCAL mode" + + def test_main_uses_local_when_no_api_keys(self, monkeypatch, tmp_path): + """main() uses LOCAL mode when no API keys are present.""" + import sys + from skill_seekers.cli.enhance_skill_local import main + + skill_dir = _make_skill_dir(tmp_path) + monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False) + monkeypatch.delenv("ANTHROPIC_AUTH_TOKEN", raising=False) + monkeypatch.delenv("GOOGLE_API_KEY", raising=False) + monkeypatch.delenv("OPENAI_API_KEY", raising=False) + monkeypatch.setattr(sys, "argv", ["enhance", str(skill_dir)]) + + api_called = [] + monkeypatch.setattr( + "skill_seekers.cli.enhance_skill_local._run_api_enhance", + lambda *a: api_called.append(a), + ) + + with patch("skill_seekers.cli.enhance_skill_local.LocalSkillEnhancer") as mock_enhancer: + mock_instance = MagicMock() + mock_instance.run.return_value = True + mock_enhancer.return_value = mock_instance + with pytest.raises(SystemExit): + main() + + assert api_called == [], "_run_api_enhance should not be called without API keys" From 1229ff2bafdddad59c65e088efb21f9dab0f8d14 Mon Sep 17 00:00:00 2001 From: yusyus Date: Tue, 24 Feb 2026 07:05:50 +0300 Subject: [PATCH 3/4] style: auto-format enhance_skill_local.py and test with ruff Co-Authored-By: Claude Sonnet 4.6 --- src/skill_seekers/cli/enhance_skill_local.py | 4 +--- tests/test_enhance_skill_local.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/skill_seekers/cli/enhance_skill_local.py b/src/skill_seekers/cli/enhance_skill_local.py index 1ddc59f..6fecc6e 100644 --- a/src/skill_seekers/cli/enhance_skill_local.py +++ b/src/skill_seekers/cli/enhance_skill_local.py @@ -1214,9 +1214,7 @@ def _detect_api_target() -> tuple[str, str] | None: Returns: (target, api_key) tuple if an API key is found, else None. """ - anthropic_key = os.environ.get("ANTHROPIC_API_KEY") or os.environ.get( - "ANTHROPIC_AUTH_TOKEN" - ) + anthropic_key = os.environ.get("ANTHROPIC_API_KEY") or os.environ.get("ANTHROPIC_AUTH_TOKEN") if anthropic_key: return ("claude", anthropic_key) diff --git a/tests/test_enhance_skill_local.py b/tests/test_enhance_skill_local.py index 601db7e..d17eb94 100644 --- a/tests/test_enhance_skill_local.py +++ b/tests/test_enhance_skill_local.py @@ -683,9 +683,7 @@ class TestEnhanceDispatcher: called_with["target"] = target called_with["api_key"] = api_key - monkeypatch.setattr( - "skill_seekers.cli.enhance_skill_local._run_api_enhance", fake_run_api - ) + monkeypatch.setattr("skill_seekers.cli.enhance_skill_local._run_api_enhance", fake_run_api) main() assert called_with == {"target": "gemini", "api_key": "AIza-test"} From 93ed5c79a89ad60b11200b5f3426a62b583e21a6 Mon Sep 17 00:00:00 2001 From: yusyus Date: Tue, 24 Feb 2026 07:09:22 +0300 Subject: [PATCH 4/4] chore: bump version to 3.1.2 and update CHANGELOG Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 7 +++++-- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0235dc3..11c4f6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,15 @@ All notable changes to Skill Seeker will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [3.1.2] - 2026-02-23 +## [3.1.2] - 2026-02-24 -### 🔧 Fix `create` Command Argument Forwarding & Unify Scraper Interface +### 🔧 Fix `create` Command Argument Forwarding, Gemini Model, and Enhance Dispatcher ### Fixed - **`create` command argument forwarding** — Universal flags (`--dry-run`, `--verbose`, `--quiet`, `--name`, `--description`) now work correctly across all source types. Previously, `create -p quick --dry-run`, `create owner/repo --dry-run`, and `create ./path --dry-run` would crash because sub-scrapers didn't accept those flags - **`skill-seekers analyze --dry-run`** — Fixed `_handle_analyze_command()` in `main.py` not forwarding `--dry-run`, `--preset`, `--quiet`, `--name`, `--description`, `--api-key`, and workflow flags to codebase_scraper +- **Gemini model 404 errors** — Replaced retired `gemini-2.0-flash-exp` with `gemini-2.5-flash` (stable GA) in the Gemini adaptor. Users attempting Gemini enhancement were getting 404 Not Found errors +- **`skill-seekers enhance` auto-detection** — The documented behaviour of auto-detecting API vs LOCAL mode was never implemented. `enhance` now correctly routes to the platform API when a key is present: `ANTHROPIC_API_KEY` → Claude API, `GOOGLE_API_KEY` → Gemini API, `OPENAI_API_KEY` → OpenAI API, no key → LOCAL mode (Claude Code Max, free). Use `--mode LOCAL` to force local mode regardless ### Added - **Shared argument contract** — New `add_all_standard_arguments(parser)` in `arguments/common.py` registers common + behavior + workflow args on any parser as a single call @@ -20,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **`--dry-run` for PDF scraper** — `skill-seekers pdf --name test --dry-run` now previews the operation - **`--verbose`/`--quiet` for GitHub and PDF scrapers** — Logging level control now works consistently across all scrapers - **`--name`/`--description` for codebase analyzer** — Custom skill name and description can now be passed to `skill-seekers analyze` +- **`--mode LOCAL` flag for `skill-seekers enhance`** — Explicitly forces LOCAL mode even when API keys are present ### Changed - **Argument deduplication** — Removed duplicated argument definitions from `arguments/github.py`, `arguments/scrape.py`, `arguments/analyze.py`, `arguments/pdf.py`; all now import shared args from `arguments/common.py` diff --git a/pyproject.toml b/pyproject.toml index fa01832..f405532 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "skill-seekers" -version = "3.1.1" +version = "3.1.2" description = "Convert documentation websites, GitHub repositories, and PDFs into Claude AI skills. International support with Chinese (įŽ€äŊ“中文) documentation." readme = "README.md" requires-python = ">=3.10"