diff --git a/.gitignore b/.gitignore index 475c26c44..4a8294fa6 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,7 @@ !/renderdoc/ !/cloudcompare/ !/openscreen/ +!/n8n/ # Step 5: Inside each software dir, ignore everything (including dotfiles) /gimp/* @@ -153,6 +154,8 @@ /wiremock/.* /exa/* /exa/.* +/n8n/* +/n8n/.* # Step 6: ...except agent-harness/ !/gimp/agent-harness/ @@ -190,6 +193,7 @@ !/wiremock/ !/wiremock/agent-harness/ !/exa/agent-harness/ +!/n8n/agent-harness/ # Step 7: Ignore build artifacts within allowed dirs **/__pycache__/ @@ -223,8 +227,5 @@ assets/gen_typing_gif.py /notebooklm/* /notebooklm/.* !/notebooklm/agent-harness/ -!/intelwatch/agent-harness/ !/intelwatch/ -!/exa/ -!/n8n/ -!/n8n/agent-harness/ +!/intelwatch/agent-harness/ diff --git a/n8n/agent-harness/CHANGELOG.md b/n8n/agent-harness/CHANGELOG.md deleted file mode 100644 index 176fc7745..000000000 --- a/n8n/agent-harness/CHANGELOG.md +++ /dev/null @@ -1,205 +0,0 @@ -# Changelog - -## [2.2.0] - 2026-04-02 - -### Hardening (review round 6) -- **Defensive dict access**: Replaced ALL `dict['key']` with `dict.get('key', default)` in display code for external API data (templates, npm nodes, bulk operations). Prevents KeyError crashes from malformed API responses. -- 15+ unsafe dict accesses fixed across template search, node search, node info, bulk activate/deactivate, and scaffold display - -## [2.1.9] - 2026-04-02 - -### Fixes (manual review + round 6) -- **API key in config show**: No longer shows partial key preview — just "configured" or empty -- Consistent with health --diagnostic which already used this approach - -## [2.1.8] - 2026-04-02 - -### Fixes (adversarial security bounty — round 5) -- **REPL quoted args**: REPL now uses `shlex.split()` — handles `'{"name": "my workflow"}'` correctly -- **Error message leaking**: Server error responses no longer leak raw text (only JSON "message" field, truncated to 200 chars) - -## [2.1.7] - 2026-04-02 - -### Critical Fixes (adversarial bug bounty — round 5) -- **Silent data corruption in autofix**: Connection key fix ("0"->"main") now MERGES instead of overwriting existing "main" connections -- **Rollback could reactivate workflows**: `versions rollback` now strips `active` field — use `activate` command explicitly -- **Scaffold pattern mutation**: `get_scaffold()` now returns deep copy — calling code can't corrupt templates -- **Lying test fixed**: `test_export_import_roundtrip` now uses real `_clean_for_api()` instead of reimplementing the logic - -## [2.1.6] - 2026-04-02 - -### Bug Fixes (review round 4) -- **IndexError in `patch --connect`**: Fixed crash when node's "main" connection list exists but is empty -- **AttributeError in `patch --remove-node/--disconnect`**: Connection list items can be None/malformed — now filtered safely -- **JSONDecodeError in config load**: Corrupted `config.json` no longer crashes the entire CLI — falls back to defaults - -## [2.1.5] - 2026-04-02 - -### Critical Bug Fixes (review round 3 — code agent) -- **Data corruption in autofix**: `_set_nested()` now correctly handles bracket notation (`assignments[0].value`) — previously created corrupt keys like `"assignments[0]"` instead of updating list items -- **Crash on malformed workflows**: `autofix()` no longer crashes when `nodes` or `connections` are `None` — handles gracefully -- Hardened all node/connection iteration with type checks -- Added regression tests for both bugs - -## [2.1.4] - 2026-04-01 - -### Security Fixes (review round 3) -- **Prevent accidental workflow activation**: `create`, `update`, `import`, `restore-all`, `template deploy` now explicitly force `active=False`. Use `activate` command to enable. -- **npm registry data validation**: Truncate and validate all fields from untrusted npm responses -- **template deploy**: Force `active=False` on deployed templates - -## [2.1.3] - 2026-04-01 - -### Security Fixes (review round 2 — security agent) -- **Path traversal**: 3 additional file reads in diff/validate/autofix now use centralized `_load_json_arg()` with `Path.resolve()` -- **Webhook URL sanitization**: Strip special chars from webhook path to prevent URL manipulation -- **Error message leaking**: Removed raw server response from error output - -## [2.1.2] - 2026-04-01 - -### Fixes (from review round 2) -- **SQLite race condition**: Added WAL journal mode + 10s timeout for concurrent access -- **Filename encoding**: Safe filename sanitization for workflows with unicode/special chars -- **Watch busy-loop**: Reject `--interval 0` to prevent CPU burn -- **Duplicate dict key**: Removed duplicate `api_key_set` in health diagnostic -- **Versions diff same**: Warn when comparing version with itself -- Added `_safe_filename()` helper for cross-platform filename safety - -## [2.1.1] - 2026-04-01 - -### Security Fixes (from 3-agent code review) -- **Path traversal prevention**: `_load_json_arg()` now resolves paths before opening -- **API key no longer exposed**: `health --diagnostic` shows only "configured/NOT SET" -- **File permissions**: config.json (0600) and versions.db directory (0700) are now restricted -- **Specific exceptions**: Replaced bare `except Exception: pass` with targeted exception types - -### Code Quality Fixes -- Extracted `_clean_for_api()` helper — eliminated 7 duplicated filter expressions -- Added `_INTERNAL_FIELDS` constant for workflow metadata fields -- Fixed `versions_diff` missing `@click.pass_context` decorator -- Fixed `_iter_params` to detect expressions inside lists (not just dicts) -- Reviewed by: code-reviewer, security-reviewer, python-reviewer agents - -## [2.1.0] - 2026-04-01 - -### Added -- `node search ` — search 26,000+ n8n community node packages on npm -- `node info ` — get package details, nodes provided, install command -- New module: `core/nodes.py` - -## [2.0.0] - 2026-04-01 - -### Added -- `workflow scaffold ` — generate workflows from 5 proven patterns (webhook, api, database, ai-agent, scheduled) -- `workflow patterns` — list available scaffold patterns -- `expression ` — validate n8n expression syntax offline -- New modules: `core/scaffolds.py`, `core/expressions.py` - -### Changed -- Version bump to 2.0.0 — the CLI is now feature-complete for the n8n Public API -- 10 core modules, 60+ commands, 80+ tests - -## [1.7.0] - 2026-04-01 - -### Added -- `workflow versions list/show/rollback/diff/prune/stats` — full version tracking with local SQLite -- Auto-snapshot before every write operation (update, patch, autofix --apply) -- Rollback to any previous version with automatic pre-rollback backup -- Version diff between any two stored snapshots -- Storage stats and pruning for disk management - -## [1.6.0] - 2026-04-01 - -### Added -- `workflow autofix` — detect and repair 6 types of common issues (expression format, webhook paths, broken connections, duplicate names, numeric connection keys, unused error outputs). Preview mode by default. -- `workflow patch` — incremental updates: `--rename`, `--enable-node`, `--disable-node`, `--remove-node`, `--connect`, `--disconnect`. No need to send the full workflow JSON. -- `health` — full health check with response time, connectivity test, and `--diagnostic` mode -- New module `core/fixers.py` with pluggable fix engine - -### Inspired by -- `WorkflowAutoFixer` and `WorkflowDiffEngine` from [n8n-mcp](https://github.com/czlonkowski/n8n-mcp) - -## [1.5.0] - 2026-04-01 - -### Added -- `template search ` — search 2,700+ templates on n8n.io -- `template get ` — view template details -- `template deploy ` — deploy template directly to your n8n instance -- `workflow validate ` — validate workflow structure (nodes, connections, triggers, duplicates) -- `workflow test ` — trigger webhook-based workflows with test data - -### Inspired by -- Features from [n8n-mcp](https://github.com/czlonkowski/n8n-mcp) adapted for CLI usage - -## [1.4.0] - 2026-04-01 - -### Added -- `workflow backup-all` — backup ALL workflows to a folder (disaster recovery) -- `workflow restore-all` — restore workflows from a backup folder (with `--dry-run`) -- `workflow diff` — compare two workflows or a workflow vs a local file (colored diff) -- `execution errors` — quick view of recent failures with optional `--details` - -## [1.3.0] - 2026-04-01 - -### Added -- `config test` — verify your n8n connection works before doing anything -- `workflow search ` — find workflows by name (case-insensitive) -- `workflow bulk-activate --tag X` / `--search X` — activate multiple workflows at once -- `workflow bulk-deactivate --tag X` / `--search X` — deactivate multiple workflows at once -- `completions bash|zsh|fish` — generate shell completion scripts -- Colored status/active columns in table output (green=success, red=error, etc.) - -## [1.2.0] - 2026-04-01 - -### Added -- `workflow export` — save any workflow to a portable JSON file -- `workflow import` — create a workflow from a JSON file (with optional name override) -- `execution watch` — live monitoring of executions with real-time polling -- `status` — dashboard showing workflows, recent executions, and errors at a glance -- REPL tab-completion for all commands and subcommands -- GitHub Actions CI (Python 3.10-3.13) - -## [1.1.0] - 2026-03-31 - -### Changed -- **BREAKING**: Removed `credential list` and `credential update` commands (not supported by n8n Public API v1.1.1) -- **BREAKING**: Removed `execution stop` command (not in public API) -- **BREAKING**: Removed `table` command group (Data Tables not in public API v1.1.1) -- Aligned all endpoints with verified n8n OpenAPI spec v1.1.1 -- Version bumped to 1.1.0 - -### Added -- `workflow set-tags` command to update workflow tags -- `workflow transfer` command to transfer workflows between projects -- `credential transfer` command to transfer credentials between projects -- URL validation in `config set base_url` -- Configurable timeout via `N8N_TIMEOUT` env var (default: 30s) -- Ellipsis indicator when table columns are truncated -- Better JSON error messages (`@file.json` not found, invalid JSON) - -### Fixed -- `credential list` no longer crashes with 405 (command removed — API doesn't support it) -- `_load_json_arg` now shows clear error for missing files and invalid JSON -- API key is always masked in `config show`, even with `--json` - -### Removed -- `setup.py` (replaced by `pyproject.toml`) -- `core/session.py` (unused REPL state dataclass) -- `core/tables.py` (Data Tables not in n8n Public API) -- `execution stop/stop-all` (not in public API) -- `execution tags/set-tags` (not in public API) - -## [1.0.0] - 2026-03-31 - -### Added -- Initial release -- CLI harness following CLI-Anything pattern -- Workflow management (list, get, create, update, delete, activate, deactivate, tags) -- Execution management (list, get, delete, retry) -- Credential management (create, delete, schema) -- Variable management (CRUD) -- Tag management (CRUD) -- Interactive REPL mode with colored output -- JSON output mode (`--json`) -- Config persistence (`~/.cli-anything/n8n/config.json`) -- 29 unit tests, E2E test suite diff --git a/n8n/agent-harness/LICENSE b/n8n/agent-harness/LICENSE deleted file mode 100644 index 420954035..000000000 --- a/n8n/agent-harness/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026 Juan Jose Sanchez Bernal / Webcomunica Soluciones Informaticas - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/n8n/agent-harness/N8N.md b/n8n/agent-harness/N8N.md deleted file mode 100644 index 038f75939..000000000 --- a/n8n/agent-harness/N8N.md +++ /dev/null @@ -1,63 +0,0 @@ -# N8N.md — Architecture - -## Overview - -`cli-anything-n8n` wraps the n8n Public API v1.1.1 into a Click-based CLI, following the CLI-Anything harness pattern by HKUDS. Only verified endpoints from the OpenAPI spec are exposed. - -## n8n Compatibility - -- **Minimum version**: n8n >= 1.0.0 -- **API version**: Public API v1.1.1 -- **Verified against**: n8n 2.43.0 -- **OpenAPI spec**: fetched from `/api/v1/openapi.yml` - -## Architecture - -``` -cli_anything/n8n/ -├── n8n_cli.py # Click CLI (groups, commands, REPL, error handling) -├── core/ # Business logic (thin wrappers over backend) -│ ├── workflows.py # 9 endpoints (CRUD, activate, tags, transfer) -│ ├── executions.py # 4 endpoints (list, get, delete, retry) -│ ├── credentials.py # 4 endpoints (create, delete, schema, transfer) -│ ├── variables.py # 4 endpoints (CRUD) -│ ├── tags.py # 5 endpoints (CRUD) -│ └── project.py # Config load/save -├── utils/ -│ ├── n8n_backend.py # HTTP client (sole module making requests) -│ └── repl_skin.py # Banner, colors, table formatting -├── skills/ -│ └── SKILL.md # AI agent discovery -└── tests/ - ├── test_core.py # Unit tests (mocked) - └── test_full_e2e.py # E2E tests (live n8n) -``` - -## Data Flow - -``` -User/Agent -> n8n_cli.py (Click) -> core/*.py -> n8n_backend.py -> n8n REST API -``` - -## Authentication - -Header-based: `X-N8N-API-KEY: ` - -Resolution order: CLI `--api-key` > env `N8N_API_KEY` > config file - -## What's NOT in the Public API - -These n8n features are NOT available through the public REST API v1.1.1: -- **Data Tables**: no endpoints at all -- **Credential listing/updating**: only create, delete, schema, transfer -- **Execution stop**: only list, get, delete, retry -- **Workflow execute**: not in public API (use internal API or n8n UI) -- **Audit logs**: POST-only, not exposed in CLI - -## Conventions - -- All core functions accept `base_url` and `api_key` as keyword args -- All commands support `--json` for parseable output -- PEP 420 namespace: `cli_anything/` has NO `__init__.py` -- Entry point: `cli-anything-n8n` -> `cli_anything.n8n.n8n_cli:main` -- Timeout configurable via `N8N_TIMEOUT` env var (default: 30s) diff --git a/n8n/agent-harness/cli_anything/n8n/README.md b/n8n/agent-harness/cli_anything/n8n/README.md index 257ece798..91a962289 100644 --- a/n8n/agent-harness/cli_anything/n8n/README.md +++ b/n8n/agent-harness/cli_anything/n8n/README.md @@ -6,84 +6,141 @@ [![CLI-Anything](https://img.shields.io/badge/CLI--Anything-harness-orange.svg)](https://github.com/HKUDS/CLI-Anything) [![n8n API v1.1.1](https://img.shields.io/badge/n8n-API%20v1.1.1-EA4B71.svg)](https://docs.n8n.io/api/api-reference/) -> [Leer en Español](#spanish) | [Read in English](#english) - ---- - - - -## English - Control your [n8n](https://n8n.io) instance from the terminal. List workflows, check executions, manage tags — all without opening the browser. Built with the [CLI-Anything](https://github.com/HKUDS/CLI-Anything) pattern, so AI agents can use it too. -### Try it now (2 minutes) +## Installation ```bash -# 1. Install pip install cli-anything-n8n +``` -# 2. Connect to your n8n +## Quick Start + +```bash +# Connect to your n8n export N8N_BASE_URL=https://your-n8n-instance.com export N8N_API_KEY=your-api-key -# 3. Try it! +# Try it! cli-anything-n8n workflow list ``` > **Where do I get my API key?** In n8n, go to Settings > API > Create API Key. -### What can I do? +## JSON Output Mode + +All commands support `--json` for machine-readable output: ```bash -# List your workflows -cli-anything-n8n workflow list - -# See only active workflows -cli-anything-n8n workflow list --active - -# Check failed executions -cli-anything-n8n execution list --status error - -# Get details of a specific workflow -cli-anything-n8n workflow get ABC123 - -# Create a tag -cli-anything-n8n tag create "production" - -# Get JSON output (for scripts or AI agents) cli-anything-n8n --json workflow list +cli-anything-n8n --json execution list --status error +``` -# Interactive mode — just type commands +## Interactive REPL + +```bash cli-anything-n8n n8n> workflow list n8n> tag list n8n> exit ``` -### All commands +## Command Groups -| Group | Commands | What it does | -|-------|----------|--------------| -| **workflow** | list, get, create, update, delete, activate, deactivate, tags, set-tags, transfer | Manage your workflows | -| **execution** | list, get, delete, retry | Check and retry executions | -| **credential** | create, delete, schema, transfer | Manage credentials | -| **variable** | list, create, update, delete | Manage environment variables | -| **tag** | list, get, create, update, delete | Organize with tags | -| **config** | show, set | Save your connection settings | - -### Save your connection (so you don't type it every time) - -```bash -cli-anything-n8n config set base_url https://your-n8n-instance.com -cli-anything-n8n config set api_key your-api-key - -# Now just use it directly -cli-anything-n8n workflow list +### Workflow Management +``` +workflow list - List all workflows +workflow get - Get workflow details +workflow create - Create a workflow from JSON +workflow update - Update a workflow from JSON +workflow delete - Delete a workflow +workflow activate - Activate a workflow +workflow deactivate - Deactivate a workflow +workflow tags - List tags on a workflow +workflow set-tags - Set tags on a workflow +workflow transfer - Transfer ownership +workflow export - Export to file +workflow import - Import from file +workflow backup-all - Backup all workflows +workflow restore-all - Restore from backup +workflow diff - Compare two workflows +workflow bulk-activate - Activate multiple workflows +workflow bulk-deactivate - Deactivate multiple workflows +workflow validate - Validate workflow structure +workflow autofix - Auto-fix common issues +workflow patch - Patch workflow fields +workflow test - Test a webhook workflow +workflow scaffold - Create from built-in patterns +workflow patterns - List available patterns +workflow versions - Local version snapshots (list/show/rollback/diff/prune/stats) ``` -### Configuration options +### Execution Management +``` +execution list - List executions +execution get - Get execution details +execution delete - Delete an execution +execution retry - Retry a failed execution +execution errors - List failed executions +execution watch - Watch executions in real-time +``` + +### Template Management +``` +template search - Search n8n.io templates +template get - Get template details +template deploy - Deploy a template +``` + +### Node Discovery +``` +node search - Search community nodes +node info - Get node package details +``` + +### Credential Management +``` +credential create - Create a credential +credential delete - Delete a credential +credential schema - Show credential schema +credential transfer - Transfer ownership +``` + +### Variable Management +``` +variable list - List variables +variable create - Create a variable +variable update - Update a variable +variable delete - Delete a variable +``` + +### Tag Management +``` +tag list - List tags +tag get - Get tag details +tag create - Create a tag +tag update - Update a tag +tag delete - Delete a tag +``` + +### Configuration +``` +config show - Show current configuration +config set - Set a configuration value +config test - Test the connection +``` + +### Standalone +``` +status - Show n8n instance status +health - Health check +expression - Validate n8n expressions offline +completions - Generate shell completions +``` + +## Configuration | Method | Priority | Example | |--------|----------|---------| @@ -93,157 +150,50 @@ cli-anything-n8n workflow list Extra env vars: `N8N_TIMEOUT` (default: 30 seconds) -### For AI agents and scripts +## Running Tests ```bash -# Always use --json for machine-readable output -cli-anything-n8n --json workflow list +# From the agent-harness directory: -# Pass complex data from files -cli-anything-n8n workflow create @my-workflow.json +# Run all unit tests +python3 -m pytest cli_anything/n8n/tests/test_core.py -v -# Check exit codes -cli-anything-n8n workflow get ABC123 && echo "OK" || echo "FAILED" -``` - -### n8n compatibility - -Verified against **n8n 2.43.0** (Public API v1.1.1). Works with any n8n >= 1.0.0. - -> **Note**: Some n8n features (Data Tables, credential listing, execution stop) are not available through the public API. This CLI only exposes verified, working endpoints. - -### Development - -```bash -git clone https://github.com/webcomunicasolutions/cli-n8n.git -cd cli-n8n -pip install -e . - -# Run tests (no n8n needed) -pip install pytest -pytest cli_anything/n8n/tests/test_core.py -v - -# Run E2E tests (needs a running n8n) +# Run E2E tests (needs a running n8n instance) export N8N_BASE_URL=https://your-n8n.com export N8N_API_KEY=your-key -pytest cli_anything/n8n/tests/test_full_e2e.py -v +python3 -m pytest cli_anything/n8n/tests/test_full_e2e.py -v ``` -### Project structure +## Architecture ``` cli_anything/n8n/ -├── n8n_cli.py # CLI + interactive REPL +├── n8n_cli.py # CLI + interactive REPL (55+ commands) ├── core/ # API wrappers (one file per resource) │ ├── workflows.py │ ├── executions.py │ ├── credentials.py │ ├── variables.py │ ├── tags.py +│ ├── templates.py +│ ├── nodes.py +│ ├── versions.py # Local SQLite version snapshots +│ ├── fixers.py # Workflow autofix engine +│ ├── scaffolds.py # Built-in workflow patterns +│ ├── expressions.py # Offline expression validator │ └── project.py # Config management ├── utils/ │ ├── n8n_backend.py # HTTP client -│ └── repl_skin.py # Terminal UI +│ └── repl_skin.py # Terminal UI (standard cli-anything skin) +├── skills/ +│ └── SKILL.md # Agent discovery metadata └── tests/ - ├── test_core.py # Unit tests (mocked) + ├── test_core.py # Unit tests (mocked HTTP) └── test_full_e2e.py # E2E tests (live n8n) ``` -### License +## n8n Compatibility -MIT - Juan Jose Sanchez Bernal / [Webcomunica Soluciones Informaticas](https://webcomunica.solutions) +Verified against **n8n 2.43.0** (Public API v1.1.1). Works with any n8n >= 1.0.0. ---- - - - -## Español - -Controla tu instancia de [n8n](https://n8n.io) desde la terminal. Lista workflows, revisa ejecuciones, gestiona tags — todo sin abrir el navegador. - -Construido con el patron [CLI-Anything](https://github.com/HKUDS/CLI-Anything), para que agentes IA tambien puedan usarlo. - -### Pruebalo ahora (2 minutos) - -```bash -# 1. Instalar -pip install cli-anything-n8n - -# 2. Conectar a tu n8n -export N8N_BASE_URL=https://tu-instancia-n8n.com -export N8N_API_KEY=tu-api-key - -# 3. Probar! -cli-anything-n8n workflow list -``` - -> **Donde consigo mi API key?** En n8n, ve a Settings > API > Create API Key. - -### Que puedo hacer? - -```bash -# Listar workflows -cli-anything-n8n workflow list - -# Solo los activos -cli-anything-n8n workflow list --active - -# Ver ejecuciones fallidas -cli-anything-n8n execution list --status error - -# Detalle de un workflow -cli-anything-n8n workflow get ABC123 - -# Crear un tag -cli-anything-n8n tag create "produccion" - -# Salida JSON (para scripts o agentes IA) -cli-anything-n8n --json workflow list - -# Modo interactivo -cli-anything-n8n -n8n> workflow list -n8n> tag list -n8n> exit -``` - -### Todos los comandos - -| Grupo | Comandos | Que hace | -|-------|----------|----------| -| **workflow** | list, get, create, update, delete, activate, deactivate, tags, set-tags, transfer | Gestionar workflows | -| **execution** | list, get, delete, retry | Revisar y reintentar ejecuciones | -| **credential** | create, delete, schema, transfer | Gestionar credenciales | -| **variable** | list, create, update, delete | Gestionar variables de entorno | -| **tag** | list, get, create, update, delete | Organizar con tags | -| **config** | show, set | Guardar configuracion de conexion | - -### Guardar conexion (para no escribirla cada vez) - -```bash -cli-anything-n8n config set base_url https://tu-instancia-n8n.com -cli-anything-n8n config set api_key tu-api-key - -# Ahora usalo directamente -cli-anything-n8n workflow list -``` - -### Compatibilidad con n8n - -Verificado contra **n8n 2.43.0** (API publica v1.1.1). Funciona con cualquier n8n >= 1.0.0. - -> **Nota**: Algunas funcionalidades de n8n (Data Tables, listar credenciales, parar ejecuciones) no estan disponibles via la API publica. Este CLI solo expone endpoints verificados y funcionales. - -### Desarrollo - -```bash -git clone https://github.com/webcomunicasolutions/cli-n8n.git -cd cli-n8n -pip install -e . -pip install pytest -pytest cli_anything/n8n/tests/test_core.py -v -``` - -### Licencia - -MIT - Juan Jose Sanchez Bernal / [Webcomunica Soluciones Informaticas](https://webcomunica.solutions) +> **Note**: Some n8n features (Data Tables, credential listing, execution stop) are not available through the public API. This CLI only exposes verified, working endpoints. diff --git a/n8n/agent-harness/cli_anything/n8n/tests/test_full_e2e.py b/n8n/agent-harness/cli_anything/n8n/tests/test_full_e2e.py index 2a1a91a95..df5e68f16 100644 --- a/n8n/agent-harness/cli_anything/n8n/tests/test_full_e2e.py +++ b/n8n/agent-harness/cli_anything/n8n/tests/test_full_e2e.py @@ -51,7 +51,7 @@ class TestCLISubprocess: [*_resolve_cli(), "--version"], capture_output=True, text=True, timeout=10, ) assert result.returncode == 0 - assert "2.4.6" in result.stdout + assert "2.4.7" in result.stdout def test_workflow_help(self): result = subprocess.run( diff --git a/n8n/agent-harness/cli_anything/n8n/utils/repl_skin.py b/n8n/agent-harness/cli_anything/n8n/utils/repl_skin.py index 24c78aa91..be7e0751e 100644 --- a/n8n/agent-harness/cli_anything/n8n/utils/repl_skin.py +++ b/n8n/agent-harness/cli_anything/n8n/utils/repl_skin.py @@ -1,41 +1,573 @@ -"""REPL UI — banner, prompt, colors, table formatting.""" +"""cli-anything REPL Skin — Unified terminal interface for all CLI harnesses. -from __future__ import annotations +Copy this file into your CLI package at: + cli_anything//utils/repl_skin.py + +Usage: + from cli_anything..utils.repl_skin import ReplSkin + + skin = ReplSkin("n8n", version="2.4.7") + skin.print_banner() # auto-detects skills/SKILL.md inside the package + prompt_text = skin.prompt(project_name="my_workflow", modified=True) + skin.success("Workflow activated") + skin.error("Connection failed") + skin.warning("Unsaved changes") + skin.info("Processing 24 workflows...") + skin.status("Status", "Connected") + skin.table(headers, rows) + skin.print_goodbye() +""" import json +import os import shutil +import sys from typing import Any import click +# ── ANSI color codes (no external deps for core styling) ────────────── -# --- Colors --- +_RESET = "\033[0m" +_BOLD = "\033[1m" +_DIM = "\033[2m" +_ITALIC = "\033[3m" +_UNDERLINE = "\033[4m" -ORANGE = "bright_yellow" -GREEN = "green" -RED = "red" -CYAN = "cyan" -DIM = "bright_black" +# Brand colors +_CYAN = "\033[38;5;80m" # cli-anything brand cyan +_CYAN_BG = "\033[48;5;80m" +_WHITE = "\033[97m" +_GRAY = "\033[38;5;245m" +_DARK_GRAY = "\033[38;5;240m" +_LIGHT_GRAY = "\033[38;5;250m" + +# Software accent colors — each software gets a unique accent +_ACCENT_COLORS = { + "gimp": "\033[38;5;214m", # warm orange + "blender": "\033[38;5;208m", # deep orange + "inkscape": "\033[38;5;39m", # bright blue + "audacity": "\033[38;5;33m", # navy blue + "libreoffice": "\033[38;5;40m", # green + "obs_studio": "\033[38;5;55m", # purple + "kdenlive": "\033[38;5;69m", # slate blue + "shotcut": "\033[38;5;35m", # teal green + "n8n": "\033[38;5;203m", # n8n coral/red (#EA4B71) +} +_DEFAULT_ACCENT = "\033[38;5;75m" # default sky blue + +# Status colors +_GREEN = "\033[38;5;78m" +_YELLOW = "\033[38;5;220m" +_RED = "\033[38;5;196m" +_BLUE = "\033[38;5;75m" +_MAGENTA = "\033[38;5;176m" + +# ── Brand icon ──────────────────────────────────────────────────────── + +# The cli-anything icon: a small colored diamond/chevron mark +_ICON = f"{_CYAN}{_BOLD}◆{_RESET}" +_ICON_SMALL = f"{_CYAN}▸{_RESET}" + +# ── Box drawing characters ──────────────────────────────────────────── + +_H_LINE = "─" +_V_LINE = "│" +_TL = "╭" +_TR = "╮" +_BL = "╰" +_BR = "╯" +_T_DOWN = "┬" +_T_UP = "┴" +_T_RIGHT = "├" +_T_LEFT = "┤" +_CROSS = "┼" -# --- Banner --- - -BANNER = r""" - _ __ | (_) __ _ _ __ _ _| |_| |__ (_)_ __ __ _ _ __ | |_ _ __ - / __| | | |___ / _` | '_ \| | | | __| '_ \| | '_ \ / _` | | '_ \| | | '_ \ -| (__ | | | | (_| | | | | |_| | |_| | | | | | | | (_| | | | | | |_| | | | - \___|_|_| \__,_|_| |_|\__, |\__|_| |_|_|_| |_|\__, | |_| |_|_|_|_| |_| - |___/ |___/ -""" +def _strip_ansi(text: str) -> str: + """Remove ANSI escape codes for length calculation.""" + import re + return re.sub(r"\033\[[^m]*m", "", text) -def print_banner(base_url: str) -> None: - click.secho(BANNER, fg=ORANGE) - click.secho(f" Connected to: {base_url}", fg=GREEN) - click.secho(" Type 'help' for commands, 'exit' to quit.\n", fg=DIM) +def _visible_len(text: str) -> int: + """Get visible length of text (excluding ANSI codes).""" + return len(_strip_ansi(text)) -# --- Output helpers --- +class ReplSkin: + """Unified REPL skin for cli-anything CLIs. + + Provides consistent branding, prompts, and message formatting + across all CLI harnesses built with the cli-anything methodology. + """ + + def __init__(self, software: str, version: str = "1.0.0", + history_file: str | None = None, skill_path: str | None = None): + """Initialize the REPL skin. + + Args: + software: Software name (e.g., "gimp", "shotcut", "blender"). + version: CLI version string. + history_file: Path for persistent command history. + Defaults to ~/.cli-anything-/history + skill_path: Path to the SKILL.md file for agent discovery. + Auto-detected from the package's skills/ directory if not provided. + Displayed in banner for AI agents to know where to read skill info. + """ + self.software = software.lower().replace("-", "_") + self.display_name = software.replace("_", " ").title() + self.version = version + + # Auto-detect skill path from package layout: + # cli_anything//utils/repl_skin.py (this file) + # cli_anything//skills/SKILL.md (target) + if skill_path is None: + from pathlib import Path + _auto = Path(__file__).resolve().parent.parent / "skills" / "SKILL.md" + if _auto.is_file(): + skill_path = str(_auto) + self.skill_path = skill_path + self.accent = _ACCENT_COLORS.get(self.software, _DEFAULT_ACCENT) + + # History file + if history_file is None: + from pathlib import Path + hist_dir = Path.home() / f".cli-anything-{self.software}" + hist_dir.mkdir(parents=True, exist_ok=True) + self.history_file = str(hist_dir / "history") + else: + self.history_file = history_file + + # Detect terminal capabilities + self._color = self._detect_color_support() + + def _detect_color_support(self) -> bool: + """Check if terminal supports color.""" + if os.environ.get("NO_COLOR"): + return False + if os.environ.get("CLI_ANYTHING_NO_COLOR"): + return False + if not hasattr(sys.stdout, "isatty"): + return False + return sys.stdout.isatty() + + def _c(self, code: str, text: str) -> str: + """Apply color code if colors are supported.""" + if not self._color: + return text + return f"{code}{text}{_RESET}" + + # ── Banner ──────────────────────────────────────────────────────── + + def print_banner(self): + """Print the startup banner with branding.""" + inner = 54 + + def _box_line(content: str) -> str: + """Wrap content in box drawing, padding to inner width.""" + pad = inner - _visible_len(content) + vl = self._c(_DARK_GRAY, _V_LINE) + return f"{vl}{content}{' ' * max(0, pad)}{vl}" + + top = self._c(_DARK_GRAY, f"{_TL}{_H_LINE * inner}{_TR}") + bot = self._c(_DARK_GRAY, f"{_BL}{_H_LINE * inner}{_BR}") + + # Title: ◆ cli-anything · N8N + icon = self._c(_CYAN + _BOLD, "◆") + brand = self._c(_CYAN + _BOLD, "cli-anything") + dot = self._c(_DARK_GRAY, "·") + name = self._c(self.accent + _BOLD, self.display_name) + title = f" {icon} {brand} {dot} {name}" + + ver = f" {self._c(_DARK_GRAY, f' v{self.version}')}" + tip = f" {self._c(_DARK_GRAY, ' Type help for commands, quit to exit')}" + empty = "" + + # Skill path for agent discovery + skill_line = None + if self.skill_path: + skill_icon = self._c(_MAGENTA, "◇") + skill_label = self._c(_DARK_GRAY, " Skill:") + skill_path_display = self._c(_LIGHT_GRAY, self.skill_path) + skill_line = f" {skill_icon} {skill_label} {skill_path_display}" + + print(top) + print(_box_line(title)) + print(_box_line(ver)) + if skill_line: + print(_box_line(skill_line)) + print(_box_line(empty)) + print(_box_line(tip)) + print(bot) + print() + + # ── Prompt ──────────────────────────────────────────────────────── + + def prompt(self, project_name: str = "", modified: bool = False, + context: str = "") -> str: + """Build a styled prompt string for prompt_toolkit or input(). + + Args: + project_name: Current project name (empty if none open). + modified: Whether the project has unsaved changes. + context: Optional extra context to show in prompt. + + Returns: + Formatted prompt string. + """ + parts = [] + + # Icon + if self._color: + parts.append(f"{_CYAN}◆{_RESET} ") + else: + parts.append("> ") + + # Software name + parts.append(self._c(self.accent + _BOLD, self.software)) + + # Project context + if project_name or context: + ctx = context or project_name + mod = "*" if modified else "" + parts.append(f" {self._c(_DARK_GRAY, '[')}") + parts.append(self._c(_LIGHT_GRAY, f"{ctx}{mod}")) + parts.append(self._c(_DARK_GRAY, ']')) + + parts.append(self._c(_GRAY, " ❯ ")) + + return "".join(parts) + + def prompt_tokens(self, project_name: str = "", modified: bool = False, + context: str = ""): + """Build prompt_toolkit formatted text tokens for the prompt. + + Use with prompt_toolkit's FormattedText for proper ANSI handling. + + Returns: + list of (style, text) tuples for prompt_toolkit. + """ + tokens = [] + + tokens.append(("class:icon", "◆ ")) + tokens.append(("class:software", self.software)) + + if project_name or context: + ctx = context or project_name + mod = "*" if modified else "" + tokens.append(("class:bracket", " [")) + tokens.append(("class:context", f"{ctx}{mod}")) + tokens.append(("class:bracket", "]")) + + tokens.append(("class:arrow", " ❯ ")) + + return tokens + + def get_prompt_style(self): + """Get a prompt_toolkit Style object matching the skin. + + Returns: + prompt_toolkit.styles.Style + """ + try: + from prompt_toolkit.styles import Style + except ImportError: + return None + + accent_hex = _ANSI_256_TO_HEX.get(self.accent, "#5fafff") + + return Style.from_dict({ + "icon": "#5fdfdf bold", # cyan brand color + "software": f"{accent_hex} bold", + "bracket": "#585858", + "context": "#bcbcbc", + "arrow": "#808080", + # Completion menu + "completion-menu.completion": "bg:#303030 #bcbcbc", + "completion-menu.completion.current": f"bg:{accent_hex} #000000", + "completion-menu.meta.completion": "bg:#303030 #808080", + "completion-menu.meta.completion.current": f"bg:{accent_hex} #000000", + # Auto-suggest + "auto-suggest": "#585858", + # Bottom toolbar + "bottom-toolbar": "bg:#1c1c1c #808080", + "bottom-toolbar.text": "#808080", + }) + + # ── Messages ────────────────────────────────────────────────────── + + def success(self, message: str): + """Print a success message with green checkmark.""" + icon = self._c(_GREEN + _BOLD, "✓") + print(f" {icon} {self._c(_GREEN, message)}") + + def error(self, message: str): + """Print an error message with red cross.""" + icon = self._c(_RED + _BOLD, "✗") + print(f" {icon} {self._c(_RED, message)}", file=sys.stderr) + + def warning(self, message: str): + """Print a warning message with yellow triangle.""" + icon = self._c(_YELLOW + _BOLD, "⚠") + print(f" {icon} {self._c(_YELLOW, message)}") + + def info(self, message: str): + """Print an info message with blue dot.""" + icon = self._c(_BLUE, "●") + print(f" {icon} {self._c(_LIGHT_GRAY, message)}") + + def hint(self, message: str): + """Print a subtle hint message.""" + print(f" {self._c(_DARK_GRAY, message)}") + + def section(self, title: str): + """Print a section header.""" + print() + print(f" {self._c(self.accent + _BOLD, title)}") + print(f" {self._c(_DARK_GRAY, _H_LINE * len(title))}") + + # ── Status display ──────────────────────────────────────────────── + + def status(self, label: str, value: str): + """Print a key-value status line.""" + lbl = self._c(_GRAY, f" {label}:") + val = self._c(_WHITE, f" {value}") + print(f"{lbl}{val}") + + def status_block(self, items: dict[str, str], title: str = ""): + """Print a block of status key-value pairs. + + Args: + items: Dict of label -> value pairs. + title: Optional title for the block. + """ + if title: + self.section(title) + + max_key = max(len(k) for k in items) if items else 0 + for label, value in items.items(): + lbl = self._c(_GRAY, f" {label:<{max_key}}") + val = self._c(_WHITE, f" {value}") + print(f"{lbl}{val}") + + def progress(self, current: int, total: int, label: str = ""): + """Print a simple progress indicator. + + Args: + current: Current step number. + total: Total number of steps. + label: Optional label for the progress. + """ + pct = int(current / total * 100) if total > 0 else 0 + bar_width = 20 + filled = int(bar_width * current / total) if total > 0 else 0 + bar = "\u2588" * filled + "\u2591" * (bar_width - filled) + text = f" {self._c(_CYAN, bar)} {self._c(_GRAY, f'{pct:3d}%')}" + if label: + text += f" {self._c(_LIGHT_GRAY, label)}" + print(text) + + # ── Table display ───────────────────────────────────────────────── + + def table(self, headers: list[str], rows: list[list[str]], + max_col_width: int = 40): + """Print a formatted table with box-drawing characters. + + Args: + headers: Column header strings. + rows: List of rows, each a list of cell strings. + max_col_width: Maximum column width before truncation. + """ + if not headers: + return + + # Calculate column widths + col_widths = [min(len(h), max_col_width) for h in headers] + for row in rows: + for i, cell in enumerate(row): + if i < len(col_widths): + col_widths[i] = min( + max(col_widths[i], len(str(cell))), max_col_width + ) + + def pad(text: str, width: int) -> str: + t = str(text)[:width] + return t + " " * (width - len(t)) + + # Header + header_cells = [ + self._c(_CYAN + _BOLD, pad(h, col_widths[i])) + for i, h in enumerate(headers) + ] + sep = self._c(_DARK_GRAY, f" {_V_LINE} ") + header_line = f" {sep.join(header_cells)}" + print(header_line) + + # Separator + print(self._c(_DARK_GRAY, f" {'───'.join([_H_LINE * w for w in col_widths])}")) + + # Rows + for row in rows: + cells = [] + for i, cell in enumerate(row): + if i < len(col_widths): + cells.append(self._c(_LIGHT_GRAY, pad(str(cell), col_widths[i]))) + row_sep = self._c(_DARK_GRAY, f" {_V_LINE} ") + print(f" {row_sep.join(cells)}") + + # ── Help display ────────────────────────────────────────────────── + + def help(self, commands: dict[str, str]): + """Print a formatted help listing. + + Args: + commands: Dict of command -> description pairs. + """ + self.section("Commands") + max_cmd = max(len(c) for c in commands) if commands else 0 + for cmd, desc in commands.items(): + cmd_styled = self._c(self.accent, f" {cmd:<{max_cmd}}") + desc_styled = self._c(_GRAY, f" {desc}") + print(f"{cmd_styled}{desc_styled}") + print() + + # ── Goodbye ─────────────────────────────────────────────────────── + + def print_goodbye(self): + """Print a styled goodbye message.""" + print(f"\n {_ICON_SMALL} {self._c(_GRAY, 'Goodbye!')}\n") + + # ── Prompt toolkit session factory ──────────────────────────────── + + def create_prompt_session(self): + """Create a prompt_toolkit PromptSession with skin styling. + + Returns: + A configured PromptSession, or None if prompt_toolkit unavailable. + """ + try: + from prompt_toolkit import PromptSession + from prompt_toolkit.history import FileHistory + from prompt_toolkit.auto_suggest import AutoSuggestFromHistory + + style = self.get_prompt_style() + + session = PromptSession( + history=FileHistory(self.history_file), + auto_suggest=AutoSuggestFromHistory(), + style=style, + enable_history_search=True, + ) + return session + except ImportError: + return None + + def get_input(self, pt_session, project_name: str = "", + modified: bool = False, context: str = "") -> str: + """Get input from user using prompt_toolkit or fallback. + + Args: + pt_session: A prompt_toolkit PromptSession (or None). + project_name: Current project name. + modified: Whether project has unsaved changes. + context: Optional context string. + + Returns: + User input string (stripped). + """ + if pt_session is not None: + from prompt_toolkit.formatted_text import FormattedText + tokens = self.prompt_tokens(project_name, modified, context) + return pt_session.prompt(FormattedText(tokens)).strip() + else: + raw_prompt = self.prompt(project_name, modified, context) + return input(raw_prompt).strip() + + # ── Toolbar builder ─────────────────────────────────────────────── + + def bottom_toolbar(self, items: dict[str, str]): + """Create a bottom toolbar callback for prompt_toolkit. + + Args: + items: Dict of label -> value pairs to show in toolbar. + + Returns: + A callable that returns FormattedText for the toolbar. + """ + def toolbar(): + from prompt_toolkit.formatted_text import FormattedText + parts = [] + for i, (k, v) in enumerate(items.items()): + if i > 0: + parts.append(("class:bottom-toolbar.text", " │ ")) + parts.append(("class:bottom-toolbar.text", f" {k}: ")) + parts.append(("class:bottom-toolbar", v)) + return FormattedText(parts) + return toolbar + + +# ── ANSI 256-color to hex mapping (for prompt_toolkit styles) ───────── + +_ANSI_256_TO_HEX = { + "\033[38;5;33m": "#0087ff", # audacity navy blue + "\033[38;5;35m": "#00af5f", # shotcut teal + "\033[38;5;39m": "#00afff", # inkscape bright blue + "\033[38;5;40m": "#00d700", # libreoffice green + "\033[38;5;55m": "#5f00af", # obs purple + "\033[38;5;69m": "#5f87ff", # kdenlive slate blue + "\033[38;5;75m": "#5fafff", # default sky blue + "\033[38;5;80m": "#5fd7d7", # brand cyan + "\033[38;5;203m": "#ff5f5f", # n8n coral + "\033[38;5;208m": "#ff8700", # blender deep orange + "\033[38;5;214m": "#ffaf00", # gimp warm orange +} + + +# ── Module-level convenience wrappers ───────────────────────────────── +# These delegate to a lazily-initialized ReplSkin singleton so that +# n8n_cli.py can do `from ...repl_skin import success, error, warn` +# while still routing through the standard ReplSkin class. + +_skin: ReplSkin | None = None + + +def _get_skin() -> ReplSkin: + global _skin + if _skin is None: + # Import VERSION lazily to avoid circular imports + try: + from cli_anything.n8n.n8n_cli import VERSION + except ImportError: + VERSION = "0.0.0" + _skin = ReplSkin("n8n", version=VERSION) + return _skin + + +def print_banner() -> None: + """Print the cli-anything branded banner.""" + _get_skin().print_banner() + + +def success(msg: str) -> None: + """Print a success message.""" + _get_skin().success(msg) + + +def error(msg: str) -> None: + """Print an error message.""" + _get_skin().error(msg) + + +def warn(msg: str) -> None: + """Print a warning message.""" + _get_skin().warning(msg) + + +# ── n8n-specific output helpers ─────────────────────────────────────── +# These handle --json flag and n8n API response formatting. +# Not part of the standard ReplSkin because they depend on click and +# n8n API response structure (data/nextCursor pagination). def output(data: Any, as_json: bool) -> None: """Print data as JSON or human-readable.""" @@ -47,7 +579,7 @@ def output(data: Any, as_json: bool) -> None: if "data" in data and isinstance(data["data"], list): _print_table(data["data"]) if "nextCursor" in data: - click.secho(f"\n Next cursor: {data['nextCursor']}", fg=DIM) + click.secho(f"\n Next cursor: {data['nextCursor']}", fg="bright_black") else: _print_dict(data) elif isinstance(data, list): @@ -63,18 +595,18 @@ def _print_dict(d: dict[str, Any], indent: int = 0) -> None: prefix = " " * indent for k, v in d.items(): if isinstance(v, dict): - click.secho(f"{prefix}{k}:", fg=CYAN) + click.secho(f"{prefix}{k}:", fg="cyan") _print_dict(v, indent + 1) elif isinstance(v, list) and v and isinstance(v[0], dict): - click.secho(f"{prefix}{k}:", fg=CYAN) + click.secho(f"{prefix}{k}:", fg="cyan") _print_table(v) else: - click.echo(f"{prefix}{click.style(str(k), fg=CYAN)}: {v}") + click.echo(f"{prefix}{click.style(str(k), fg='cyan')}: {v}") def _print_table(rows: list[dict[str, Any]]) -> None: if not rows: - click.secho(" (empty)", fg=DIM) + click.secho(" (empty)", fg="bright_black") return term_width = shutil.get_terminal_size().columns @@ -97,14 +629,17 @@ def _print_table(rows: list[dict[str, Any]]) -> None: max_col = max(10, term_width // len(simple_keys) - 3) col_widths = {k: min(v, max_col) for k, v in col_widths.items()} - header = " | ".join(click.style(k.ljust(col_widths[k])[:col_widths[k]], fg=CYAN) for k in simple_keys) + header = " | ".join( + click.style(k.ljust(col_widths[k])[:col_widths[k]], fg="cyan") + for k in simple_keys + ) click.echo(header) click.echo("-+-".join("-" * col_widths[k] for k in simple_keys)) # Color rules for specific columns color_rules = { - "status": {"success": GREEN, "error": RED, "running": ORANGE, "waiting": CYAN}, - "active": {"True": GREEN, "False": DIM}, + "status": {"success": "green", "error": "red", "running": "bright_yellow", "waiting": "cyan"}, + "active": {"True": "green", "False": "bright_black"}, } for row in rows: @@ -121,15 +656,3 @@ def _print_table(rows: list[dict[str, Any]]) -> None: cell = click.style(cell, fg=color_rules[k][v]) vals.append(cell) click.echo(" | ".join(vals)) - - -def success(msg: str) -> None: - click.secho(f" OK: {msg}", fg=GREEN) - - -def error(msg: str) -> None: - click.secho(f" ERROR: {msg}", fg=RED, err=True) - - -def warn(msg: str) -> None: - click.secho(f" WARN: {msg}", fg=ORANGE) diff --git a/n8n/agent-harness/setup.py b/n8n/agent-harness/setup.py new file mode 100644 index 000000000..d3cf87b88 --- /dev/null +++ b/n8n/agent-harness/setup.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +""" +Setup script for cli-anything-n8n + +Install (dev mode): + pip install -e . + +Build: + python -m build + +Publish: + twine upload dist/* +""" + +from pathlib import Path +from setuptools import setup, find_namespace_packages + +ROOT = Path(__file__).parent +README = ROOT / "cli_anything/n8n/README.md" + +long_description = README.read_text(encoding="utf-8") if README.exists() else "" + +setup( + name="cli-anything-n8n", + version="2.4.7", + description="CLI harness for n8n workflow automation — n8n REST API v1.1.1", + long_description=long_description, + long_description_content_type="text/markdown", + + author="Juan Jose Sanchez Bernal", + author_email="info@webcomunica.solutions", + url="https://github.com/HKUDS/CLI-Anything", + + project_urls={ + "Source": "https://github.com/HKUDS/CLI-Anything", + "Tracker": "https://github.com/HKUDS/CLI-Anything/issues", + "PyPI": "https://pypi.org/project/cli-anything-n8n/", + }, + + license="MIT", + + packages=find_namespace_packages(include=("cli_anything.*",)), + + python_requires=">=3.10", + + install_requires=[ + "click>=8.1", + "prompt-toolkit>=3.0", + "requests>=2.28", + ], + + extras_require={ + "dev": [ + "pytest>=7", + "pytest-cov>=4", + ], + }, + + entry_points={ + "console_scripts": [ + "cli-anything-n8n=cli_anything.n8n.n8n_cli:main", + ], + }, + package_data={ + "cli_anything.n8n": ["skills/*.md"], + }, + include_package_data=True, + zip_safe=False, + + keywords=[ + "cli", + "n8n", + "workflow", + "automation", + "cli-anything", + ], + + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries :: Python Modules", + "License :: OSI Approved :: MIT License", + + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + ], +)