@saulwade/swl-ses 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +238 -0
- package/README.md +560 -0
- package/_userland/agentes/.gitkeep +0 -0
- package/_userland/habilidades/.gitkeep +0 -0
- package/agentes/.evolved.json +9 -0
- package/agentes/accesibilidad-wcag-swl.md +692 -0
- package/agentes/arquitecto-swl.md +238 -0
- package/agentes/auto-evolucion-swl.md +854 -0
- package/agentes/backend-api-swl.md +470 -0
- package/agentes/backend-csharp-swl.md +418 -0
- package/agentes/backend-go-swl.md +388 -0
- package/agentes/backend-java-swl.md +279 -0
- package/agentes/backend-node-swl.md +477 -0
- package/agentes/backend-python-swl.md +608 -0
- package/agentes/backend-rust-swl.md +362 -0
- package/agentes/backend-workers-swl.md +480 -0
- package/agentes/cloud-infra-swl.md +485 -0
- package/agentes/consolidador-swl.md +539 -0
- package/agentes/datos-swl.md +584 -0
- package/agentes/depurador-swl.md +349 -0
- package/agentes/devops-ci-swl.md +374 -0
- package/agentes/disenador-ui-swl.md +558 -0
- package/agentes/documentador-swl.md +343 -0
- package/agentes/evals/arquitecto-swl.evals.json +56 -0
- package/agentes/evals/auto-evolucion-swl.evals.json +68 -0
- package/agentes/evals/implementador-swl.evals.json +56 -0
- package/agentes/evals/orquestador-swl.evals.json +60 -0
- package/agentes/evals/perfilador-usuario-swl.evals.json +60 -0
- package/agentes/evals/red-team-swl.evals.json +59 -0
- package/agentes/evals/revisor-codigo-swl.evals.json +59 -0
- package/agentes/frontend-angular-swl.md +627 -0
- package/agentes/frontend-css-swl.md +720 -0
- package/agentes/frontend-react-swl.md +696 -0
- package/agentes/frontend-swl.md +500 -0
- package/agentes/frontend-tailwind-swl.md +830 -0
- package/agentes/implementador-swl.md +328 -0
- package/agentes/investigador-swl.md +430 -0
- package/agentes/investigador-ux-swl.md +500 -0
- package/agentes/llm-apps-swl.md +276 -0
- package/agentes/migrador-swl.md +417 -0
- package/agentes/mobile-android-swl.md +509 -0
- package/agentes/mobile-cross-swl.md +539 -0
- package/agentes/mobile-ios-swl.md +500 -0
- package/agentes/mobile-testing-swl.md +300 -0
- package/agentes/notificador-swl.md +916 -0
- package/agentes/observabilidad-swl.md +436 -0
- package/agentes/orquestador-swl.md +884 -0
- package/agentes/pagos-swl.md +283 -0
- package/agentes/perfilador-usuario-swl.md +306 -0
- package/agentes/planificador-swl.md +402 -0
- package/agentes/producto-prd-swl.md +587 -0
- package/agentes/red-team-swl.md +216 -0
- package/agentes/release-manager-swl.md +568 -0
- package/agentes/rendimiento-swl.md +714 -0
- package/agentes/resolutor-build-swl.md +243 -0
- package/agentes/revisor-angular-swl.md +276 -0
- package/agentes/revisor-codigo-swl.md +348 -0
- package/agentes/revisor-csharp-swl.md +262 -0
- package/agentes/revisor-go-swl.md +257 -0
- package/agentes/revisor-java-swl.md +255 -0
- package/agentes/revisor-kotlin-swl.md +271 -0
- package/agentes/revisor-nextjs-swl.md +279 -0
- package/agentes/revisor-php-swl.md +269 -0
- package/agentes/revisor-react-swl.md +276 -0
- package/agentes/revisor-rust-swl.md +344 -0
- package/agentes/revisor-seguridad-swl.md +390 -0
- package/agentes/revisor-swift-swl.md +266 -0
- package/agentes/revisor-typescript-swl.md +344 -0
- package/agentes/sre-swl.md +265 -0
- package/agentes/tdd-qa-swl.md +354 -0
- package/agentes/ux-disenador-swl.md +501 -0
- package/bin/lib/bot-comandos.js +1030 -0
- package/bin/lib/bot-discovery.js +182 -0
- package/bin/lib/bot-git.js +142 -0
- package/bin/swl-ses.js +325 -0
- package/bin/swl-telegram-bot.js +442 -0
- package/bin/swl-telegram-bot.plist +21 -0
- package/bin/swl-telegram-bot.service +14 -0
- package/comandos/swl/.evolved.json +23 -0
- package/comandos/swl/actualizar.md +174 -0
- package/comandos/swl/adoptar-proyecto.md +207 -0
- package/comandos/swl/aprender.md +701 -0
- package/comandos/swl/auditar-deps.md +134 -0
- package/comandos/swl/autoresearch.md +170 -0
- package/comandos/swl/ayuda.md +224 -0
- package/comandos/swl/brainstorm.md +50 -0
- package/comandos/swl/checkpoint.md +330 -0
- package/comandos/swl/compactar.md +283 -0
- package/comandos/swl/configurar-ci.md +227 -0
- package/comandos/swl/contexto.md +112 -0
- package/comandos/swl/contribuir.md +233 -0
- package/comandos/swl/crear-skill.md +292 -0
- package/comandos/swl/cron.md +196 -0
- package/comandos/swl/dashboard.md +146 -0
- package/comandos/swl/discutir-fase.md +230 -0
- package/comandos/swl/ejecutar-fase.md +135 -0
- package/comandos/swl/evaluar-skill.md +487 -0
- package/comandos/swl/evolucion-estado.md +142 -0
- package/comandos/swl/evolucionar.md +259 -0
- package/comandos/swl/exportar-vault.md +189 -0
- package/comandos/swl/gateway.md +158 -0
- package/comandos/swl/inbox.md +116 -0
- package/comandos/swl/instalar.md +220 -0
- package/comandos/swl/instintos.md +86 -0
- package/comandos/swl/mapear-codebase.md +312 -0
- package/comandos/swl/mcp-status.md +175 -0
- package/comandos/swl/metricas.md +270 -0
- package/comandos/swl/modelo.md +102 -0
- package/comandos/swl/notificaciones.md +396 -0
- package/comandos/swl/nuevo-proyecto.md +154 -0
- package/comandos/swl/planear-fase.md +221 -0
- package/comandos/swl/plugins.md +256 -0
- package/comandos/swl/reflect-skills.md +125 -0
- package/comandos/swl/release.md +217 -0
- package/comandos/swl/revisar-impacto.md +206 -0
- package/comandos/swl/revisar.md +330 -0
- package/comandos/swl/salud.md +363 -0
- package/comandos/swl/sesiones.md +200 -0
- package/comandos/swl/skill-search.md +113 -0
- package/comandos/swl/verificar.md +585 -0
- package/comandos/swl/wiki.md +620 -0
- package/contextos/dev.md +32 -0
- package/contextos/research.md +30 -0
- package/contextos/review.md +31 -0
- package/habilidades/accesibilidad-a11y/SKILL.md +201 -0
- package/habilidades/accesibilidad-a11y/evals/evals.json +56 -0
- package/habilidades/accesibilidad-a11y/recursos/ejemplos-y-checklist-completo.md +441 -0
- package/habilidades/agent-browser/SKILL.md +218 -0
- package/habilidades/agentes-como-servicio/SKILL.md +218 -0
- package/habilidades/ai-runtime-security/SKILL.md +273 -0
- package/habilidades/angular-avanzado/SKILL.md +164 -0
- package/habilidades/angular-avanzado/recursos/ejemplos-avanzados.md +219 -0
- package/habilidades/angular-moderno/SKILL.md +186 -0
- package/habilidades/angular-moderno/evals/evals.json +45 -0
- package/habilidades/angular-moderno/recursos/ejemplos-avanzados.md +106 -0
- package/habilidades/api-rest-diseno/SKILL.md +191 -0
- package/habilidades/api-rest-diseno/recursos/openapi-template.yaml +506 -0
- package/habilidades/api-rest-diseno/recursos/referencia-api.md +140 -0
- package/habilidades/aprendizaje-continuo/SKILL.md +151 -0
- package/habilidades/aprendizaje-continuo/evals/evals.json +53 -0
- package/habilidades/aprendizaje-continuo/recursos/referencia-instintos.md +290 -0
- package/habilidades/async-python/SKILL.md +149 -0
- package/habilidades/async-python/evals/evals.json +47 -0
- package/habilidades/async-python/recursos/patrones-y-ejemplos-completos.md +292 -0
- package/habilidades/auth-patrones/.evolved.json +9 -0
- package/habilidades/auth-patrones/SKILL.md +413 -0
- package/habilidades/auth-patrones/recursos/implementaciones-completas.md +229 -0
- package/habilidades/auto-evolucion-protocolo/SKILL.md +276 -0
- package/habilidades/auto-evolucion-protocolo/evals/evals.json +55 -0
- package/habilidades/auto-evolucion-protocolo/recursos/referencia-completa.md +145 -0
- package/habilidades/autoresearch/SKILL.md +268 -0
- package/habilidades/autoresearch/evals/evals.json +41 -0
- package/habilidades/autoresearch/recursos/checklist-template.md +191 -0
- package/habilidades/autoresearch/scripts/calcular-score.js +88 -0
- package/habilidades/azure-cloud/SKILL.md +308 -0
- package/habilidades/azure-cloud/recursos/aks.md +327 -0
- package/habilidades/backend-mcp-servidor/SKILL.md +270 -0
- package/habilidades/backend-production-resilience/SKILL.md +288 -0
- package/habilidades/brainstorming/SKILL.md +295 -0
- package/habilidades/brainstorming/recursos/componentes-html.md +247 -0
- package/habilidades/build-errors-cpp/SKILL.md +270 -0
- package/habilidades/build-errors-csharp/SKILL.md +265 -0
- package/habilidades/build-errors-go/SKILL.md +306 -0
- package/habilidades/build-errors-java/SKILL.md +278 -0
- package/habilidades/build-errors-kotlin/SKILL.md +303 -0
- package/habilidades/build-errors-nextjs/SKILL.md +312 -0
- package/habilidades/build-errors-php/SKILL.md +270 -0
- package/habilidades/build-errors-python/SKILL.md +292 -0
- package/habilidades/build-errors-rust/SKILL.md +284 -0
- package/habilidades/build-errors-swift/SKILL.md +272 -0
- package/habilidades/build-errors-typescript/SKILL.md +369 -0
- package/habilidades/checklist-calidad/SKILL.md +271 -0
- package/habilidades/checklist-calidad/recursos/quality-report-template.md +148 -0
- package/habilidades/checklist-seguridad/SKILL.md +285 -0
- package/habilidades/checkpoints-verificacion/SKILL.md +298 -0
- package/habilidades/checkpoints-verificacion/recursos/checkpoint-templates.md +360 -0
- package/habilidades/ci-cd-pipelines/SKILL.md +157 -0
- package/habilidades/ci-cd-pipelines/recursos/github-actions-template.yaml +403 -0
- package/habilidades/ci-cd-pipelines/recursos/pipelines-completos.md +487 -0
- package/habilidades/cloud-aws/SKILL.md +142 -0
- package/habilidades/cloud-aws/recursos/servicios-aws-referencia.md +321 -0
- package/habilidades/compactacion-contexto/SKILL.md +247 -0
- package/habilidades/contenedores-docker/SKILL.md +137 -0
- package/habilidades/contenedores-docker/recursos/dockerfile-template.dockerfile +160 -0
- package/habilidades/contenedores-docker/recursos/ejemplos-y-configuraciones.md +327 -0
- package/habilidades/context-builder/SKILL.md +170 -0
- package/habilidades/control-profundidad/SKILL.md +128 -0
- package/habilidades/csharp-experto/SKILL.md +322 -0
- package/habilidades/csharp-patrones/SKILL.md +316 -0
- package/habilidades/csharp-testing/SKILL.md +286 -0
- package/habilidades/css-moderno/SKILL.md +166 -0
- package/habilidades/css-moderno/evals/evals.json +43 -0
- package/habilidades/css-moderno/recursos/ejemplos-y-patrones-completos.md +337 -0
- package/habilidades/datos-etl/SKILL.md +129 -0
- package/habilidades/datos-etl/recursos/implementaciones-completas.md +322 -0
- package/habilidades/dbml-experto/SKILL.md +339 -0
- package/habilidades/dbml-experto/evals/evals.json +56 -0
- package/habilidades/dependencias-auditoria/SKILL.md +320 -0
- package/habilidades/deprecacion-migracion/SKILL.md +169 -0
- package/habilidades/deprecacion-migracion/recursos/implementaciones-completas.md +220 -0
- package/habilidades/design-tokens/SKILL.md +158 -0
- package/habilidades/design-tokens/recursos/tokens-y-configuracion.md +363 -0
- package/habilidades/devsecops-pipeline-security/SKILL.md +309 -0
- package/habilidades/diagrama-arquitectura/SKILL.md +165 -0
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -0
- package/habilidades/discutir-fase/SKILL.md +188 -0
- package/habilidades/diseno-herramientas-agente/SKILL.md +199 -0
- package/habilidades/diseno-responsivo/SKILL.md +186 -0
- package/habilidades/diseno-responsivo/recursos/ejemplos-layouts.md +156 -0
- package/habilidades/django-experto/SKILL.md +205 -0
- package/habilidades/django-experto/recursos/async-django.md +390 -0
- package/habilidades/django-experto/recursos/drf-patrones.md +438 -0
- package/habilidades/django-experto/recursos/orm-avanzado.md +382 -0
- package/habilidades/django-experto/recursos/referencia-completa.md +188 -0
- package/habilidades/django-experto/recursos/testing-django.md +415 -0
- package/habilidades/doc-sync/SKILL.md +280 -0
- package/habilidades/drift-detection/SKILL.md +179 -0
- package/habilidades/ejecutar-fase/SKILL.md +468 -0
- package/habilidades/estilo-sin-ai-isms/SKILL.md +775 -0
- package/habilidades/estilo-sin-ai-isms/evals/evals.json +63 -0
- package/habilidades/estilo-sin-ai-isms/scripts/detectar_aiisms.py +500 -0
- package/habilidades/estructura-proyecto-claude/SKILL.md +215 -0
- package/habilidades/estructura-proyecto-claude/recursos/claude-md-template.md +261 -0
- package/habilidades/estructura-proyecto-claude/recursos/configuracion-y-extensiones.md +176 -0
- package/habilidades/estructura-proyecto-claude/recursos/frontmatter-y-hooks-referencia.md +289 -0
- package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +77 -0
- package/habilidades/estructura-proyecto-claude/recursos/variantes-por-stack.md +177 -0
- package/habilidades/evaluacion-agentes/SKILL.md +314 -0
- package/habilidades/event-driven/SKILL.md +153 -0
- package/habilidades/event-driven/recursos/implementaciones-completas.md +423 -0
- package/habilidades/extraccion-documentos/SKILL.md +221 -0
- package/habilidades/extractor-de-aprendizajes/.evolved.json +9 -0
- package/habilidades/extractor-de-aprendizajes/SKILL.md +311 -0
- package/habilidades/extractor-de-aprendizajes/evals/evals.json +55 -0
- package/habilidades/fastapi-experto/SKILL.md +221 -0
- package/habilidades/fastapi-experto/recursos/async-patterns.md +438 -0
- package/habilidades/fastapi-experto/recursos/dependency-injection.md +330 -0
- package/habilidades/fastapi-experto/recursos/referencia-completa.md +79 -0
- package/habilidades/fastapi-experto/recursos/testing-httpx.md +420 -0
- package/habilidades/filament-admin/SKILL.md +290 -0
- package/habilidades/frontend-avanzado/SKILL.md +257 -0
- package/habilidades/frontend-avanzado/recursos/apis-nativas-ejemplos.md +341 -0
- package/habilidades/gcp-cloud/SKILL.md +260 -0
- package/habilidades/gcp-cloud/recursos/gke.md +234 -0
- package/habilidades/gcp-cloud/recursos/terraform-gcp.md +307 -0
- package/habilidades/generacion-mermaid/SKILL.md +229 -0
- package/habilidades/git-worktrees-paralelo/SKILL.md +270 -0
- package/habilidades/go-experto/SKILL.md +305 -0
- package/habilidades/go-patrones/SKILL.md +299 -0
- package/habilidades/go-testing/SKILL.md +291 -0
- package/habilidades/graphql-experto/SKILL.md +323 -0
- package/habilidades/guardrail-semantico/SKILL.md +282 -0
- package/habilidades/harness-claude-code/SKILL.md +299 -0
- package/habilidades/iam-secretos/SKILL.md +265 -0
- package/habilidades/iam-secretos/recursos/implementaciones-completas.md +356 -0
- package/habilidades/infra-github-actions/SKILL.md +166 -0
- package/habilidades/instalar-sistema/.evolved.json +9 -0
- package/habilidades/instalar-sistema/SKILL.md +221 -0
- package/habilidades/java-experto/SKILL.md +290 -0
- package/habilidades/java-patrones/SKILL.md +275 -0
- package/habilidades/java-testing/SKILL.md +288 -0
- package/habilidades/kotlin-compose/SKILL.md +278 -0
- package/habilidades/kotlin-compose/recursos/animaciones-performance.md +93 -0
- package/habilidades/kotlin-experto/SKILL.md +318 -0
- package/habilidades/kotlin-testing/SKILL.md +267 -0
- package/habilidades/kotlin-testing/recursos/testing-avanzado.md +74 -0
- package/habilidades/kubernetes-orquestacion/SKILL.md +152 -0
- package/habilidades/kubernetes-orquestacion/recursos/manifiestos-completos.md +452 -0
- package/habilidades/langchain-langraph/SKILL.md +386 -0
- package/habilidades/langchain-langraph/recursos/evaluacion-rag.md +321 -0
- package/habilidades/langchain-langraph/recursos/rag-maturity-model.md +225 -0
- package/habilidades/langchain-langraph/recursos/vectorstores.md +306 -0
- package/habilidades/legacy-code-rescue/SKILL.md +267 -0
- package/habilidades/likec4-experto/SKILL.md +412 -0
- package/habilidades/likec4-experto/evals/evals.json +69 -0
- package/habilidades/manejo-errores/.evolved.json +9 -0
- package/habilidades/manejo-errores/SKILL.md +407 -0
- package/habilidades/manejo-errores/recursos/implementaciones-completas.md +248 -0
- package/habilidades/mapear-codebase/SKILL.md +275 -0
- package/habilidades/memoria-busqueda/SKILL.md +194 -0
- package/habilidades/memoria-busqueda/evals/evals.json +44 -0
- package/habilidades/meta-skills-estandar/SKILL.md +298 -0
- package/habilidades/meta-skills-estandar/recursos/anti-patrones-y-leyes.md +205 -0
- package/habilidades/meta-skills-estandar/recursos/frameworks-seguridad.md +107 -0
- package/habilidades/meta-skills-estandar/recursos/idiomas-framework.md +60 -0
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -0
- package/habilidades/microservicios/SKILL.md +155 -0
- package/habilidades/microservicios/recursos/patrones-y-ejemplos-completos.md +325 -0
- package/habilidades/mobile-flutter/SKILL.md +199 -0
- package/habilidades/mobile-flutter/recursos/ejemplos-completos.md +319 -0
- package/habilidades/mobile-react-native/SKILL.md +176 -0
- package/habilidades/mobile-react-native/recursos/ejemplos-completos.md +216 -0
- package/habilidades/mongodb-experto/SKILL.md +302 -0
- package/habilidades/monitoring-alertas/SKILL.md +201 -0
- package/habilidades/monitoring-alertas/recursos/instrumentacion-y-alertas.md +301 -0
- package/habilidades/nestjs-experto/SKILL.md +307 -0
- package/habilidades/nestjs-experto/recursos/guards-interceptors.md +339 -0
- package/habilidades/nestjs-experto/recursos/modulos-di.md +287 -0
- package/habilidades/nestjs-experto/recursos/testing-nestjs.md +354 -0
- package/habilidades/nextjs-experto/SKILL.md +335 -0
- package/habilidades/nextjs-patrones/SKILL.md +303 -0
- package/habilidades/nextjs-testing/SKILL.md +331 -0
- package/habilidades/node-experto/.evolved.json +9 -0
- package/habilidades/node-experto/SKILL.md +266 -0
- package/habilidades/node-experto/recursos/patrones-completos.md +283 -0
- package/habilidades/notificaciones-multicanal/SKILL.md +159 -0
- package/habilidades/notificaciones-multicanal/recursos/config-template.json +115 -0
- package/habilidades/notificaciones-multicanal/recursos/configuracion-y-templates.md +303 -0
- package/habilidades/nuevo-proyecto/SKILL.md +204 -0
- package/habilidades/orquestacion-async/SKILL.md +303 -0
- package/habilidades/paid-media-tracking/SKILL.md +269 -0
- package/habilidades/paid-media-tracking/recursos/auditoria-tracking.md +220 -0
- package/habilidades/paid-media-tracking/recursos/google-ads-api.md +215 -0
- package/habilidades/patrones-python/SKILL.md +228 -0
- package/habilidades/patrones-python/evals/evals.json +56 -0
- package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -0
- package/habilidades/patrones-python/recursos/referencia-completa.md +202 -0
- package/habilidades/perfil-usuario/SKILL.md +200 -0
- package/habilidades/perfil-usuario/evals/evals.json +55 -0
- package/habilidades/performance-baseline/SKILL.md +297 -0
- package/habilidades/php-experto/SKILL.md +291 -0
- package/habilidades/php-patrones/SKILL.md +306 -0
- package/habilidades/php-testing/SKILL.md +280 -0
- package/habilidades/planear-fase/SKILL.md +269 -0
- package/habilidades/postgresql-experto/SKILL.md +151 -0
- package/habilidades/postgresql-experto/evals/evals.json +53 -0
- package/habilidades/postgresql-experto/recursos/referencia-completa.md +215 -0
- package/habilidades/prevencion-racionalizacion/SKILL.md +175 -0
- package/habilidades/prevencion-sobreingenieria/SKILL.md +323 -0
- package/habilidades/privacy-memoria/SKILL.md +141 -0
- package/habilidades/privacy-memoria/evals/evals.json +43 -0
- package/habilidades/prompt-engineering/SKILL.md +518 -0
- package/habilidades/prompt-engineering/recursos/patrones-avanzados.md +467 -0
- package/habilidades/rag-arquitectura/SKILL.md +338 -0
- package/habilidades/rails-experto/SKILL.md +237 -0
- package/habilidades/rails-experto/recursos/active-record.md +260 -0
- package/habilidades/rails-experto/recursos/hotwire-turbo.md +293 -0
- package/habilidades/rails-experto/recursos/testing-rspec.md +362 -0
- package/habilidades/react-experto/SKILL.md +209 -0
- package/habilidades/react-experto/evals/evals.json +55 -0
- package/habilidades/react-experto/recursos/patrones-y-ejemplos-completos.md +240 -0
- package/habilidades/react-optimizacion/SKILL.md +174 -0
- package/habilidades/react-optimizacion/recursos/patrones-avanzados.md +138 -0
- package/habilidades/redis-experto/SKILL.md +305 -0
- package/habilidades/release-semver/.evolved.json +9 -0
- package/habilidades/release-semver/SKILL.md +248 -0
- package/habilidades/release-semver/scripts/generar-changelog.sh +238 -0
- package/habilidades/rust-experto/SKILL.md +400 -0
- package/habilidades/rust-patrones/SKILL.md +296 -0
- package/habilidades/rust-testing/SKILL.md +311 -0
- package/habilidades/seguridad-skills-ia/SKILL.md +262 -0
- package/habilidades/sql-optimizacion/SKILL.md +200 -0
- package/habilidades/sql-optimizacion/evals/evals.json +54 -0
- package/habilidades/sql-optimizacion/recursos/patrones-sql-avanzados.md +131 -0
- package/habilidades/sre-patrones/SKILL.md +333 -0
- package/habilidades/sre-patrones/recursos/chaos-engineering.md +241 -0
- package/habilidades/sre-patrones/recursos/oncall-design.md +236 -0
- package/habilidades/stripe-pagos/SKILL.md +550 -0
- package/habilidades/stripe-pagos/recursos/errores-reintentos.md +390 -0
- package/habilidades/stripe-pagos/recursos/stripe-connect.md +290 -0
- package/habilidades/structured-outputs/SKILL.md +343 -0
- package/habilidades/swift-experto/SKILL.md +320 -0
- package/habilidades/swift-experto/recursos/keychain-y-wrappers.md +110 -0
- package/habilidades/swift-patrones/SKILL.md +313 -0
- package/habilidades/swift-patrones/recursos/tca-ejemplo-completo.md +113 -0
- package/habilidades/swift-testing/SKILL.md +254 -0
- package/habilidades/swift-testing/recursos/xcuitest-planes.md +143 -0
- package/habilidades/swl-dashboard/SKILL.md +370 -0
- package/habilidades/swl-markitdown/SKILL.md +285 -0
- package/habilidades/swl-markitdown/evals/evals.json +52 -0
- package/habilidades/swl-revisar-impacto/SKILL.md +233 -0
- package/habilidades/tailwind-experto/SKILL.md +240 -0
- package/habilidades/tailwind-experto/recursos/referencia-completa.md +184 -0
- package/habilidades/tdd-workflow/SKILL.md +293 -0
- package/habilidades/terraform-experto/SKILL.md +321 -0
- package/habilidades/testing-python/SKILL.md +340 -0
- package/habilidades/testing-python/recursos/ejemplos-completos.md +167 -0
- package/habilidades/threat-model-lite/SKILL.md +246 -0
- package/habilidades/tracing-processor/SKILL.md +212 -0
- package/habilidades/tracking-measurement/SKILL.md +239 -0
- package/habilidades/tracking-measurement/recursos/consent-mode.md +231 -0
- package/habilidades/tracking-measurement/recursos/gtm-datalayer.md +216 -0
- package/habilidades/tracking-measurement/recursos/meta-capi.md +262 -0
- package/habilidades/typescript-avanzado/SKILL.md +144 -0
- package/habilidades/typescript-avanzado/evals/evals.json +55 -0
- package/habilidades/typescript-avanzado/recursos/patrones-y-ejemplos-completos.md +298 -0
- package/habilidades/typescript-diagnosticos/SKILL.md +513 -0
- package/habilidades/ux-diseno/SKILL.md +116 -0
- package/habilidades/ux-diseno/evals/evals.json +43 -0
- package/habilidades/ux-diseno/recursos/patrones-ux-referencia.md +214 -0
- package/habilidades/validacion-ci-sistema/SKILL.md +136 -0
- package/habilidades/validacion-ci-sistema/recursos/validadores-completos.md +369 -0
- package/habilidades/validacion-ci-sistema/scripts/validar-sistema.sh +286 -0
- package/habilidades/verificacion-evidencia/SKILL.md +160 -0
- package/habilidades/verificar-trabajo/SKILL.md +303 -0
- package/habilidades/verificar-trabajo/recursos/plantilla-verificacion.md +60 -0
- package/habilidades/wiki-conocimiento/SKILL.md +276 -0
- package/habilidades/wireframes-flujos/SKILL.md +212 -0
- package/habilidades/wireframes-flujos/recursos/referencia-completa.md +192 -0
- package/habilidades/workflow-claude-code/SKILL.md +260 -0
- package/habilidades/workflow-claude-code/recursos/referencia-completa.md +109 -0
- package/hooks/_run-hook.sh +57 -0
- package/hooks/actualizar-perfil-usuario.js +364 -0
- package/hooks/agente-lifecycle.js +71 -0
- package/hooks/aiisms-detector.js +173 -0
- package/hooks/audit-trail.js +204 -0
- package/hooks/auto-background.js +97 -0
- package/hooks/auto-consolidacion.js +178 -0
- package/hooks/auto-evolucion.js +666 -0
- package/hooks/auto-restaurar-settings.js +360 -0
- package/hooks/calidad-pre-commit.js +929 -0
- package/hooks/calidad-typescript.js +511 -0
- package/hooks/captura-feedback-usuario.js +148 -0
- package/hooks/check-update.js +211 -0
- package/hooks/clasificador-mensajes.js +271 -0
- package/hooks/degradacion-instintos.js +272 -0
- package/hooks/escaneo-secretos.js +389 -0
- package/hooks/extraccion-aprendizajes.js +763 -0
- package/hooks/grafo-contexto.js +129 -0
- package/hooks/graph-update.js +67 -0
- package/hooks/guardrail-modelo.js +247 -0
- package/hooks/inbox-aviso.js +75 -0
- package/hooks/inyeccion-contexto.js +246 -0
- package/hooks/lib/abort-registry.js +214 -0
- package/hooks/lib/agent-backend.js +210 -0
- package/hooks/lib/agent-comms.js +263 -0
- package/hooks/lib/agent-issue-codes.js +284 -0
- package/hooks/lib/agent-matcher.js +189 -0
- package/hooks/lib/async-hook-registry.js +252 -0
- package/hooks/lib/atomic-write.js +130 -0
- package/hooks/lib/auto-consolidator.js +335 -0
- package/hooks/lib/canary-skills.js +187 -0
- package/hooks/lib/consolidation-lock.js +291 -0
- package/hooks/lib/context-builder.js +430 -0
- package/hooks/lib/context-compressor.js +657 -0
- package/hooks/lib/convergence-detector.js +105 -0
- package/hooks/lib/delegation-tracker.js +198 -0
- package/hooks/lib/detectar-package-manager.js +423 -0
- package/hooks/lib/edit-accumulator.js +171 -0
- package/hooks/lib/error-classifier.js +308 -0
- package/hooks/lib/event-bus.js +112 -0
- package/hooks/lib/evolution-tracker.js +442 -0
- package/hooks/lib/execution-state.js +316 -0
- package/hooks/lib/fingerprint-id.js +135 -0
- package/hooks/lib/gateway-notify.js +116 -0
- package/hooks/lib/graph-security.js +75 -0
- package/hooks/lib/guardrail-metrics.js +202 -0
- package/hooks/lib/hook-circuit-breaker.js +206 -0
- package/hooks/lib/loop-detector.js +267 -0
- package/hooks/lib/mcp-health.js +184 -0
- package/hooks/lib/mcp-pool.js +436 -0
- package/hooks/lib/memory-search.js +506 -0
- package/hooks/lib/merkle-audit.js +96 -0
- package/hooks/lib/model-router.js +222 -0
- package/hooks/lib/normalize-error.js +324 -0
- package/hooks/lib/normalize-input.js +65 -0
- package/hooks/lib/nudge-tracker.js +306 -0
- package/hooks/lib/otlp-exporter.js +365 -0
- package/hooks/lib/performance-marks.js +239 -0
- package/hooks/lib/privacy-filter.js +128 -0
- package/hooks/lib/prompt-injection-scanner.js +209 -0
- package/hooks/lib/provenance-tracker.js +183 -0
- package/hooks/lib/rate-limit-tracker.js +253 -0
- package/hooks/lib/reflect-classifier.js +164 -0
- package/hooks/lib/resource-quota.js +122 -0
- package/hooks/lib/retry-jitter.js +165 -0
- package/hooks/lib/risk-engine.js +368 -0
- package/hooks/lib/run-log.js +408 -0
- package/hooks/lib/session-fts.js +379 -0
- package/hooks/lib/session-store.js +293 -0
- package/hooks/lib/singleton-guard.js +159 -0
- package/hooks/lib/skill-auditor.js +588 -0
- package/hooks/lib/sync-status.js +228 -0
- package/hooks/lib/taint-tracker.js +107 -0
- package/hooks/lib/task-service.js +295 -0
- package/hooks/lib/tech-skills-map.js +146 -0
- package/hooks/lib/telegram-cliente.js +159 -0
- package/hooks/lib/telegram-config.js +170 -0
- package/hooks/lib/token-budget.js +156 -0
- package/hooks/lib/token-estimator.js +420 -0
- package/hooks/lib/toon-compressor.js +245 -0
- package/hooks/lib/usage-model.js +183 -0
- package/hooks/lib/variable-resolver.js +230 -0
- package/hooks/linea-estado.js +324 -0
- package/hooks/metricas-evolucion.js +209 -0
- package/hooks/monitor-contexto.js +325 -0
- package/hooks/notificacion-sesion-stop.js +198 -0
- package/hooks/notificacion-telegram-notification.js +4 -0
- package/hooks/notificacion-telegram-subagent.js +4 -0
- package/hooks/notificacion-telegram.js +267 -0
- package/hooks/preservar-estado-pre-compact.js +150 -0
- package/hooks/proteccion-rutas.js +366 -0
- package/hooks/registro-turnos.js +209 -0
- package/hooks/resumen-sesion.js +249 -0
- package/hooks/risk-scoring.js +323 -0
- package/hooks/rotar-audit-auto.js +122 -0
- package/hooks/sugerir-regenerar-inventario.js +170 -0
- package/hooks/telemetria-agentes.js +167 -0
- package/hooks/tracking-costos.js +688 -0
- package/instintos/global.yaml +8 -0
- package/instintos/perfil-usuario.yaml +53 -0
- package/instintos/prompt-appendices.yaml +57 -0
- package/instintos/proyecto.yaml +372 -0
- package/manifiestos/gateway-config.json +77 -0
- package/manifiestos/handoff-context.json +223 -0
- package/manifiestos/hook-profiles.json +44 -0
- package/manifiestos/hooks-config.json +360 -0
- package/manifiestos/modulos.json +1173 -0
- package/manifiestos/perfiles.json +404 -0
- package/package.json +86 -0
- package/plantillas/ESTADO.md +109 -0
- package/plantillas/HOJA-RUTA.md +143 -0
- package/plantillas/PROYECTO.md +122 -0
- package/plantillas/REQUISITOS.md +132 -0
- package/plantillas/auditor-veto-template.md +105 -0
- package/plantillas/github-workflows/README.md +47 -0
- package/plantillas/github-workflows/release-please.yml +44 -0
- package/plantillas/github-workflows/swl-ci.yml +107 -0
- package/plantillas/github-workflows/swl-security.yml +51 -0
- package/plantillas/mcp-mineru.json +13 -0
- package/plantillas/research/ARQUITECTURA.md +220 -0
- package/plantillas/research/FUNCIONALIDADES.md +175 -0
- package/plantillas/research/RESUMEN.md +165 -0
- package/plantillas/research/STACK.md +233 -0
- package/plantillas/research/TRAMPAS.md +299 -0
- package/plantillas/skill-evals-template.json +44 -0
- package/plugin.json +343 -0
- package/reglas/accesibilidad.md +269 -0
- package/reglas/api-diseno.md +400 -0
- package/reglas/arquitectura.md +352 -0
- package/reglas/brevedad-output.md +124 -0
- package/reglas/cloud-infra.md +247 -0
- package/reglas/docs.md +245 -0
- package/reglas/estilo-codigo.md +201 -0
- package/reglas/git-workflow.md +245 -0
- package/reglas/gobernanza.md +271 -0
- package/reglas/harness-claude-code.md +213 -0
- package/reglas/hooks.md +186 -0
- package/reglas/lenguajes/csharp/estilo-codigo.md +231 -0
- package/reglas/lenguajes/csharp/hooks.md +281 -0
- package/reglas/lenguajes/csharp/patrones.md +226 -0
- package/reglas/lenguajes/csharp/seguridad.md +258 -0
- package/reglas/lenguajes/csharp/testing.md +176 -0
- package/reglas/lenguajes/go/estilo-codigo.md +195 -0
- package/reglas/lenguajes/go/hooks.md +249 -0
- package/reglas/lenguajes/go/patrones.md +249 -0
- package/reglas/lenguajes/go/seguridad.md +225 -0
- package/reglas/lenguajes/go/testing.md +272 -0
- package/reglas/lenguajes/java/estilo-codigo.md +217 -0
- package/reglas/lenguajes/java/hooks.md +251 -0
- package/reglas/lenguajes/java/patrones.md +226 -0
- package/reglas/lenguajes/java/seguridad.md +233 -0
- package/reglas/lenguajes/java/testing.md +238 -0
- package/reglas/lenguajes/kotlin/estilo-codigo.md +208 -0
- package/reglas/lenguajes/kotlin/hooks.md +245 -0
- package/reglas/lenguajes/kotlin/patrones.md +201 -0
- package/reglas/lenguajes/kotlin/seguridad.md +202 -0
- package/reglas/lenguajes/kotlin/testing.md +236 -0
- package/reglas/lenguajes/nextjs/estilo-codigo.md +175 -0
- package/reglas/lenguajes/nextjs/hooks.md +186 -0
- package/reglas/lenguajes/nextjs/patrones.md +225 -0
- package/reglas/lenguajes/nextjs/seguridad.md +216 -0
- package/reglas/lenguajes/nextjs/testing.md +193 -0
- package/reglas/lenguajes/php/estilo-codigo.md +228 -0
- package/reglas/lenguajes/php/hooks.md +165 -0
- package/reglas/lenguajes/php/patrones.md +233 -0
- package/reglas/lenguajes/php/seguridad.md +186 -0
- package/reglas/lenguajes/php/testing.md +205 -0
- package/reglas/lenguajes/rust/estilo-codigo.md +207 -0
- package/reglas/lenguajes/rust/hooks.md +240 -0
- package/reglas/lenguajes/rust/patrones.md +250 -0
- package/reglas/lenguajes/rust/seguridad.md +221 -0
- package/reglas/lenguajes/rust/testing.md +194 -0
- package/reglas/lenguajes/swift/estilo-codigo.md +238 -0
- package/reglas/lenguajes/swift/hooks.md +257 -0
- package/reglas/lenguajes/swift/patrones.md +235 -0
- package/reglas/lenguajes/swift/seguridad.md +248 -0
- package/reglas/lenguajes/swift/testing.md +242 -0
- package/reglas/markitdown.md +60 -0
- package/reglas/memoria-consolidada.md +209 -0
- package/reglas/patrones.md +225 -0
- package/reglas/performance.md +195 -0
- package/reglas/pruebas.md +159 -0
- package/reglas/seguridad-agentes.md +351 -0
- package/reglas/seguridad.md +151 -0
- package/reglas/skills-estandar.md +373 -0
- package/reglas/testing.md +193 -0
- package/schemas/agent-contract.json +176 -0
- package/schemas/agent-frontmatter.schema.json +149 -0
- package/schemas/agent-message.schema.json +53 -0
- package/schemas/agent-output-implementacion.schema.json +85 -0
- package/schemas/agent-output-planificacion.schema.json +113 -0
- package/schemas/agent-output-review.schema.json +78 -0
- package/schemas/diary-entry.schema.json +80 -0
- package/schemas/hook-profiles.schema.json +39 -0
- package/schemas/hooks-config.schema.json +74 -0
- package/schemas/instinct.schema.json +115 -0
- package/schemas/modulos.schema.json +29 -0
- package/schemas/perfiles.schema.json +28 -0
- package/schemas/plugin.schema.json +64 -0
- package/schemas/skill-evals.schema.json +95 -0
- package/schemas/skill-frontmatter.schema.json +170 -0
- package/scripts/actualizar.js +145 -0
- package/scripts/audit-skills.sh +78 -0
- package/scripts/auditar-agentes-gaps.js +149 -0
- package/scripts/auditar-cobertura-frameworks.js +241 -0
- package/scripts/auditar-skills-gaps.js +206 -0
- package/scripts/bootstrap-instintos.js +259 -0
- package/scripts/check-update.js +109 -0
- package/scripts/comandos/agents.js +105 -0
- package/scripts/comandos/info.js +108 -0
- package/scripts/comandos/install-asistido.js +186 -0
- package/scripts/comandos/skills.js +211 -0
- package/scripts/configurar-branch-protection.js +418 -0
- package/scripts/daemon-swl.py +388 -0
- package/scripts/desinstalar.js +130 -0
- package/scripts/doctor.js +559 -0
- package/scripts/field-report.js +199 -0
- package/scripts/generar-inventario.js +317 -0
- package/scripts/inbox-tmux-inject.js +161 -0
- package/scripts/inferir-herramientas-permitidas.js +586 -0
- package/scripts/inicializar.js +133 -0
- package/scripts/instalador.js +1031 -0
- package/scripts/instalar-git-hook.js +122 -0
- package/scripts/lib/agp-frontmatter.js +222 -0
- package/scripts/lib/append-con-marcadores.js +199 -0
- package/scripts/lib/artefactos-python.js +43 -0
- package/scripts/lib/audit-query.js +221 -0
- package/scripts/lib/autostart-linux.js +347 -0
- package/scripts/lib/autostart-macos.js +360 -0
- package/scripts/lib/autostart-windows.js +307 -0
- package/scripts/lib/budget-enforcer.js +252 -0
- package/scripts/lib/claude-sessions.js +285 -0
- package/scripts/lib/configurar-ci.js +380 -0
- package/scripts/lib/console-span-exporter.js +92 -0
- package/scripts/lib/contadores-inventario.js +217 -0
- package/scripts/lib/dashboard-widgets.js +290 -0
- package/scripts/lib/detectar-runtime.js +279 -0
- package/scripts/lib/detectar-stack.js +187 -0
- package/scripts/lib/diary-entry.js +234 -0
- package/scripts/lib/drift-detector.js +545 -0
- package/scripts/lib/estado.js +124 -0
- package/scripts/lib/gestor-componentes.js +243 -0
- package/scripts/lib/gitignore-manifest.js +305 -0
- package/scripts/lib/graph-analyze.py +556 -0
- package/scripts/lib/graph-builder.py +485 -0
- package/scripts/lib/graph-cluster.py +259 -0
- package/scripts/lib/health-row.js +168 -0
- package/scripts/lib/hooks-settings.js +789 -0
- package/scripts/lib/manifiestos.js +138 -0
- package/scripts/lib/mc-client.js +137 -0
- package/scripts/lib/notificaciones-telegram.js +1107 -0
- package/scripts/lib/npm-version.js +261 -0
- package/scripts/lib/paquetes-conocidos.js +50 -0
- package/scripts/lib/preservar-usuario.js +586 -0
- package/scripts/lib/prompt-builder.js +264 -0
- package/scripts/lib/resolver-externo.js +332 -0
- package/scripts/lib/schedule-parser.js +305 -0
- package/scripts/lib/scoring-instintos.js +240 -0
- package/scripts/lib/seguridad.js +160 -0
- package/scripts/lib/selector-interactivo.js +152 -0
- package/scripts/lib/semantic-search.js +242 -0
- package/scripts/lib/skill-discovery.js +234 -0
- package/scripts/lib/skill-metrics.js +246 -0
- package/scripts/lib/skill-normalizer.js +112 -0
- package/scripts/lib/skills-hub.js +340 -0
- package/scripts/lib/span-schema.js +134 -0
- package/scripts/lib/tool-cost-analyzer.js +255 -0
- package/scripts/lib/tracing-processor-interface.js +286 -0
- package/scripts/lib/transformadores/base.js +80 -0
- package/scripts/lib/transformadores/claude.js +124 -0
- package/scripts/lib/transformadores/codex.js +115 -0
- package/scripts/lib/transformadores/copilot.js +106 -0
- package/scripts/lib/transformadores/gemini.js +74 -0
- package/scripts/lib/transformadores/index.js +35 -0
- package/scripts/lib/transformadores/opencode.js +75 -0
- package/scripts/lib/ui.js +259 -0
- package/scripts/limpiar-artefactos-python.js +131 -0
- package/scripts/mcp-orchestrator.py +386 -0
- package/scripts/mcp-pool-manager.py +352 -0
- package/scripts/mcp-telemetry.py +378 -0
- package/scripts/poblar-evolvable.js +226 -0
- package/scripts/publicar.js +287 -0
- package/scripts/reflect-skills.js +403 -0
- package/scripts/rotar-audit-logs.js +185 -0
- package/scripts/run-skill-evals.js +242 -0
- package/scripts/smoke-test.js +374 -0
- package/scripts/token-analysis.py +471 -0
- package/scripts/validar-manifest.js +195 -0
- package/scripts/validar-memoria.js +321 -0
- package/scripts/validar-tests-aislamiento.js +184 -0
- package/scripts/validar-tokens-test.js +208 -0
- package/scripts/validar.js +147 -0
- package/scripts/validate-markdown.py +339 -0
- package/scripts/validate-skills.py +385 -0
- package/scripts/vendor/claude-usage/README.md +116 -0
- package/scripts/vendor/claude-usage/cli.py +334 -0
- package/scripts/vendor/claude-usage/dashboard.py +795 -0
- package/scripts/vendor/claude-usage/scanner.py +467 -0
- package/scripts/vendor/markitdown/cli.py +194 -0
- package/scripts/verificar-evolucion.js +289 -0
- package/scripts/verificar-release.js +494 -0
|
@@ -0,0 +1,1030 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lógica de comandos del bot de Telegram — módulo testeable.
|
|
5
|
+
*
|
|
6
|
+
* Maneja el dispatch de comandos, la generación de respuestas HTML
|
|
7
|
+
* y la gestión de proyectos silenciados. Las funciones aquí no
|
|
8
|
+
* hacen llamadas de red — reciben el resultado de getUpdates como
|
|
9
|
+
* input y retornan el texto a enviar.
|
|
10
|
+
*
|
|
11
|
+
* @module bin/lib/bot-comandos
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
|
|
19
|
+
const { descubrirProyectos, resolverProyecto } = require('./bot-discovery');
|
|
20
|
+
const { obtenerEstado, obtenerLog, obtenerDiff } = require('./bot-git');
|
|
21
|
+
const { agregarMute, eliminarMute } = require('../../hooks/lib/telegram-config');
|
|
22
|
+
|
|
23
|
+
// Límite de chars antes de truncar respuestas largas
|
|
24
|
+
const LIMITE_RESPUESTA = 3500;
|
|
25
|
+
const SUFIJO_TRUNCADO = '\n… [truncado]';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Lista de comandos disponibles (whitelist de texto de respuesta para /help).
|
|
29
|
+
*/
|
|
30
|
+
const TEXTO_AYUDA = `<b>Comandos disponibles:</b>
|
|
31
|
+
|
|
32
|
+
/proyectos — Lista proyectos detectados
|
|
33
|
+
/estado <proyecto> — Rama, estado y último commit
|
|
34
|
+
/log <proyecto> [N] — Últimos N commits (default 5, máx 25)
|
|
35
|
+
/diff <proyecto> — Archivos modificados (staged y no-staged)
|
|
36
|
+
/ultimo <proyecto> — Último mensaje del asistente
|
|
37
|
+
/resumen <proyecto> — Resumen del proyecto
|
|
38
|
+
/inbox <proyecto> — Comandos pendientes en el inbox del proyecto
|
|
39
|
+
/costo [d] — Tokens y USD del día o últimos d días
|
|
40
|
+
/salud <proyecto> — Score y advertencias del sistema (SALUD.md)
|
|
41
|
+
/silencio <proyecto> — Silenciar notificaciones del proyecto
|
|
42
|
+
/activar <proyecto> — Reactivar notificaciones del proyecto
|
|
43
|
+
/mutes — Lista proyectos silenciados
|
|
44
|
+
/quien — Mostrar tu chat_id`;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Trunca texto si supera el límite para respuestas de Telegram.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} texto - Texto a truncar.
|
|
50
|
+
* @returns {string} Texto truncado o el original si cabe.
|
|
51
|
+
*/
|
|
52
|
+
function truncarRespuesta(texto) {
|
|
53
|
+
const s = String(texto || '');
|
|
54
|
+
if (s.length <= LIMITE_RESPUESTA) return s;
|
|
55
|
+
const corte = LIMITE_RESPUESTA - SUFIJO_TRUNCADO.length;
|
|
56
|
+
return s.slice(0, corte).trimEnd() + SUFIJO_TRUNCADO;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Escapa caracteres HTML para respuestas en parse_mode HTML de Telegram.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} texto - Texto a escapar.
|
|
63
|
+
* @returns {string} Texto escapado.
|
|
64
|
+
*/
|
|
65
|
+
function escaparHtml(texto) {
|
|
66
|
+
return String(texto || '')
|
|
67
|
+
.replace(/&/g, '&')
|
|
68
|
+
.replace(/</g, '<')
|
|
69
|
+
.replace(/>/g, '>');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Parsea el texto del comando y el argumento de proyecto de un mensaje.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} texto - Texto completo del mensaje (ej: "/estado mi-proyecto").
|
|
76
|
+
* @returns {{ comando: string, argProyecto: string, argExtra: string }}
|
|
77
|
+
*/
|
|
78
|
+
function parsearMensaje(texto) {
|
|
79
|
+
const partes = (texto || '').trim().split(/\s+/);
|
|
80
|
+
// El comando puede traer @nombrebot: "/estado@mibot" → limpiar
|
|
81
|
+
const comandoRaw = (partes[0] || '').toLowerCase().split('@')[0];
|
|
82
|
+
const argProyecto = partes[1] || '';
|
|
83
|
+
const argExtra = partes[2] || '';
|
|
84
|
+
return { comando: comandoRaw, argProyecto, argExtra };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Resuelve un proyecto y devuelve texto de error si no hay match.
|
|
89
|
+
*
|
|
90
|
+
* @param {string} termino - Término de búsqueda.
|
|
91
|
+
* @returns {{ proyecto: object|null, errorTexto: string|null }}
|
|
92
|
+
*/
|
|
93
|
+
function _resolverOError(termino) {
|
|
94
|
+
if (!termino) {
|
|
95
|
+
return { proyecto: null, errorTexto: 'Falta el nombre del proyecto. Uso: /comando <proyecto>' };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const { proyecto, candidatos, ambiguo } = resolverProyecto(termino);
|
|
99
|
+
|
|
100
|
+
if (ambiguo) {
|
|
101
|
+
const lista = candidatos.map((p) => `• ${escaparHtml(p.nombre)}`).join('\n');
|
|
102
|
+
return {
|
|
103
|
+
proyecto: null,
|
|
104
|
+
errorTexto: `Varios proyectos coinciden con "<b>${escaparHtml(termino)}</b>":\n${lista}\n\nSé más específico.`,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!proyecto) {
|
|
109
|
+
return {
|
|
110
|
+
proyecto: null,
|
|
111
|
+
errorTexto: `Proyecto no encontrado: <b>${escaparHtml(termino)}</b>`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { proyecto, errorTexto: null };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// -----------------------------------------------------------------
|
|
119
|
+
// Handlers de cada comando
|
|
120
|
+
// -----------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Maneja /help y /start.
|
|
124
|
+
*
|
|
125
|
+
* @returns {string} Texto de respuesta.
|
|
126
|
+
*/
|
|
127
|
+
function cmdHelp() {
|
|
128
|
+
return TEXTO_AYUDA;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Maneja /proyectos.
|
|
133
|
+
*
|
|
134
|
+
* @returns {string} Lista de proyectos en HTML.
|
|
135
|
+
*/
|
|
136
|
+
function cmdProyectos() {
|
|
137
|
+
const proyectos = descubrirProyectos();
|
|
138
|
+
if (proyectos.length === 0) {
|
|
139
|
+
return 'No se encontraron proyectos en <code>~/.claude/projects/</code>.';
|
|
140
|
+
}
|
|
141
|
+
const lista = proyectos
|
|
142
|
+
.map((p) => `• <b>${escaparHtml(p.nombre)}</b>\n <code>${escaparHtml(p.cwd)}</code>`)
|
|
143
|
+
.join('\n');
|
|
144
|
+
return `<b>Proyectos detectados (${proyectos.length}):</b>\n\n${lista}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Maneja /estado <proyecto>.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} argProyecto - Término de búsqueda del proyecto.
|
|
151
|
+
* @returns {string} Texto de respuesta HTML.
|
|
152
|
+
*/
|
|
153
|
+
function cmdEstado(argProyecto) {
|
|
154
|
+
const { proyecto, errorTexto } = _resolverOError(argProyecto);
|
|
155
|
+
if (errorTexto) return errorTexto;
|
|
156
|
+
|
|
157
|
+
const { ok, rama, estado, ultimoCommit, error } = obtenerEstado(proyecto.cwd);
|
|
158
|
+
|
|
159
|
+
if (!ok) {
|
|
160
|
+
return `Error al consultar git en <b>${escaparHtml(proyecto.nombre)}</b>:\n<code>${escaparHtml(error)}</code>`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return truncarRespuesta(
|
|
164
|
+
`<b>${escaparHtml(proyecto.nombre)}</b>\n\n` +
|
|
165
|
+
`<b>Rama:</b> <code>${escaparHtml(rama)}</code>\n\n` +
|
|
166
|
+
`<b>Estado:</b>\n<pre>${escaparHtml(estado || '(limpio)')}</pre>\n` +
|
|
167
|
+
`<b>Último commit:</b>\n<code>${escaparHtml(ultimoCommit || '(sin commits)')}</code>`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Maneja /log <proyecto> [N].
|
|
173
|
+
*
|
|
174
|
+
* @param {string} argProyecto - Término de búsqueda del proyecto.
|
|
175
|
+
* @param {string} argN - Número de commits (opcional).
|
|
176
|
+
* @returns {string} Texto de respuesta HTML.
|
|
177
|
+
*/
|
|
178
|
+
function cmdLog(argProyecto, argN) {
|
|
179
|
+
const { proyecto, errorTexto } = _resolverOError(argProyecto);
|
|
180
|
+
if (errorTexto) return errorTexto;
|
|
181
|
+
|
|
182
|
+
const n = parseInt(argN, 10) || 5;
|
|
183
|
+
const { ok, lineas, error } = obtenerLog(proyecto.cwd, n);
|
|
184
|
+
|
|
185
|
+
if (!ok) {
|
|
186
|
+
return `Error al consultar log en <b>${escaparHtml(proyecto.nombre)}</b>:\n<code>${escaparHtml(error)}</code>`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (lineas.length === 0) {
|
|
190
|
+
return `<b>${escaparHtml(proyecto.nombre)}</b>: no hay commits.`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const cuerpo = lineas.map((l) => `• <code>${escaparHtml(l)}</code>`).join('\n');
|
|
194
|
+
return truncarRespuesta(`<b>Log de ${escaparHtml(proyecto.nombre)}:</b>\n\n${cuerpo}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Maneja /diff <proyecto>.
|
|
199
|
+
*
|
|
200
|
+
* @param {string} argProyecto - Término de búsqueda del proyecto.
|
|
201
|
+
* @returns {string} Texto de respuesta HTML.
|
|
202
|
+
*/
|
|
203
|
+
function cmdDiff(argProyecto) {
|
|
204
|
+
const { proyecto, errorTexto } = _resolverOError(argProyecto);
|
|
205
|
+
if (errorTexto) return errorTexto;
|
|
206
|
+
|
|
207
|
+
const { diffNoStaged, diffStaged } = obtenerDiff(proyecto.cwd);
|
|
208
|
+
|
|
209
|
+
let cuerpo = `<b>Diff — ${escaparHtml(proyecto.nombre)}</b>\n\n`;
|
|
210
|
+
|
|
211
|
+
if (diffNoStaged) {
|
|
212
|
+
cuerpo += `<b>Sin stage:</b>\n<pre>${escaparHtml(diffNoStaged)}</pre>\n`;
|
|
213
|
+
} else {
|
|
214
|
+
cuerpo += '<b>Sin stage:</b> (sin cambios)\n';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (diffStaged) {
|
|
218
|
+
cuerpo += `<b>Staged:</b>\n<pre>${escaparHtml(diffStaged)}</pre>`;
|
|
219
|
+
} else {
|
|
220
|
+
cuerpo += '<b>Staged:</b> (sin cambios)';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return truncarRespuesta(cuerpo);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Busca el último mensaje del asistente en el .jsonl más reciente del proyecto.
|
|
228
|
+
*
|
|
229
|
+
* @param {string} cwd - Directorio del proyecto.
|
|
230
|
+
* @returns {string|null} El texto del mensaje o null si no se encuentra.
|
|
231
|
+
*/
|
|
232
|
+
function _ultimoMensajeAsistente(cwd) {
|
|
233
|
+
// Buscar .jsonl en el directorio de proyectos Claude Code
|
|
234
|
+
const os = require('node:os');
|
|
235
|
+
const dirProyectos = path.join(os.homedir(), '.claude', 'projects');
|
|
236
|
+
|
|
237
|
+
let jsonlMasReciente = null;
|
|
238
|
+
let mtimeMasReciente = 0;
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const dirs = fs.readdirSync(dirProyectos);
|
|
242
|
+
for (const dir of dirs) {
|
|
243
|
+
const dirCompleto = path.join(dirProyectos, dir);
|
|
244
|
+
try {
|
|
245
|
+
if (!fs.statSync(dirCompleto).isDirectory()) continue;
|
|
246
|
+
const archivos = fs.readdirSync(dirCompleto);
|
|
247
|
+
for (const archivo of archivos) {
|
|
248
|
+
if (!archivo.endsWith('.jsonl')) continue;
|
|
249
|
+
const ruta = path.join(dirCompleto, archivo);
|
|
250
|
+
try {
|
|
251
|
+
const stat = fs.statSync(ruta);
|
|
252
|
+
// Verificar que el jsonl pertenece al proyecto buscado
|
|
253
|
+
const contenido = fs.readFileSync(ruta, 'utf8');
|
|
254
|
+
const lineas = contenido.split('\n');
|
|
255
|
+
for (const linea of lineas) {
|
|
256
|
+
try {
|
|
257
|
+
const obj = JSON.parse(linea);
|
|
258
|
+
if (obj && obj.cwd === cwd && stat.mtimeMs > mtimeMasReciente) {
|
|
259
|
+
jsonlMasReciente = ruta;
|
|
260
|
+
mtimeMasReciente = stat.mtimeMs;
|
|
261
|
+
}
|
|
262
|
+
} catch (_) {}
|
|
263
|
+
}
|
|
264
|
+
} catch (_) {}
|
|
265
|
+
}
|
|
266
|
+
} catch (_) {}
|
|
267
|
+
}
|
|
268
|
+
} catch (_) {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (!jsonlMasReciente) return null;
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
const lineas = fs.readFileSync(jsonlMasReciente, 'utf8').split('\n').reverse();
|
|
276
|
+
for (const linea of lineas) {
|
|
277
|
+
try {
|
|
278
|
+
const obj = JSON.parse(linea);
|
|
279
|
+
// Buscar mensajes del asistente en formato de sesión Claude Code
|
|
280
|
+
if (obj && obj.message && obj.message.role === 'assistant') {
|
|
281
|
+
const content = obj.message.content;
|
|
282
|
+
if (typeof content === 'string') return content;
|
|
283
|
+
if (Array.isArray(content)) {
|
|
284
|
+
// Concatenar bloques de texto
|
|
285
|
+
const textos = content
|
|
286
|
+
.filter((b) => b && b.type === 'text')
|
|
287
|
+
.map((b) => b.text)
|
|
288
|
+
.join('\n');
|
|
289
|
+
if (textos) return textos;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
} catch (_) {}
|
|
293
|
+
}
|
|
294
|
+
} catch (_) {}
|
|
295
|
+
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Maneja /ultimo <proyecto>.
|
|
301
|
+
*
|
|
302
|
+
* @param {string} argProyecto - Término de búsqueda del proyecto.
|
|
303
|
+
* @returns {string} Texto de respuesta HTML.
|
|
304
|
+
*/
|
|
305
|
+
function cmdUltimo(argProyecto) {
|
|
306
|
+
const { proyecto, errorTexto } = _resolverOError(argProyecto);
|
|
307
|
+
if (errorTexto) return errorTexto;
|
|
308
|
+
|
|
309
|
+
const msg = _ultimoMensajeAsistente(proyecto.cwd);
|
|
310
|
+
|
|
311
|
+
if (!msg) {
|
|
312
|
+
return `No se encontró ningún mensaje del asistente en <b>${escaparHtml(proyecto.nombre)}</b>.`;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return truncarRespuesta(
|
|
316
|
+
`<b>Último mensaje — ${escaparHtml(proyecto.nombre)}:</b>\n\n${escaparHtml(msg)}`
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Maneja /resumen <proyecto>.
|
|
322
|
+
*
|
|
323
|
+
* Busca en orden: .planning/RESUMEN.md, .planning/ESTADO.md, README.md.
|
|
324
|
+
*
|
|
325
|
+
* @param {string} argProyecto - Término de búsqueda del proyecto.
|
|
326
|
+
* @returns {string} Texto de respuesta HTML.
|
|
327
|
+
*/
|
|
328
|
+
function cmdResumen(argProyecto) {
|
|
329
|
+
const { proyecto, errorTexto } = _resolverOError(argProyecto);
|
|
330
|
+
if (errorTexto) return errorTexto;
|
|
331
|
+
|
|
332
|
+
const candidatos = [
|
|
333
|
+
path.join(proyecto.cwd, '.planning', 'RESUMEN.md'),
|
|
334
|
+
path.join(proyecto.cwd, '.planning', 'ESTADO.md'),
|
|
335
|
+
path.join(proyecto.cwd, 'README.md'),
|
|
336
|
+
];
|
|
337
|
+
|
|
338
|
+
for (const ruta of candidatos) {
|
|
339
|
+
try {
|
|
340
|
+
if (fs.existsSync(ruta)) {
|
|
341
|
+
const contenido = fs.readFileSync(ruta, 'utf8');
|
|
342
|
+
if (contenido.trim()) {
|
|
343
|
+
const nombreArchivo = path.basename(ruta);
|
|
344
|
+
return truncarRespuesta(
|
|
345
|
+
`<b>Resumen de ${escaparHtml(proyecto.nombre)} (${nombreArchivo}):</b>\n\n${escaparHtml(contenido)}`
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
} catch (_) {
|
|
350
|
+
// Siguiente candidato
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return `No se encontró resumen en <b>${escaparHtml(proyecto.nombre)}</b>.\n` +
|
|
355
|
+
`Archivos buscados: .planning/RESUMEN.md, .planning/ESTADO.md, README.md`;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Maneja /silencio <proyecto>.
|
|
360
|
+
*
|
|
361
|
+
* @param {string} argProyecto - Término de búsqueda del proyecto.
|
|
362
|
+
* @returns {string} Texto de respuesta.
|
|
363
|
+
*/
|
|
364
|
+
function cmdSilencio(argProyecto) {
|
|
365
|
+
const { proyecto, errorTexto } = _resolverOError(argProyecto);
|
|
366
|
+
if (errorTexto) return errorTexto;
|
|
367
|
+
|
|
368
|
+
agregarMute(proyecto.nombre);
|
|
369
|
+
return `Proyecto <b>${escaparHtml(proyecto.nombre)}</b> silenciado. Las notificaciones no se enviarán hasta ejecutar /activar.`;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Maneja /activar <proyecto>.
|
|
374
|
+
*
|
|
375
|
+
* @param {string} argProyecto - Término de búsqueda del proyecto.
|
|
376
|
+
* @returns {string} Texto de respuesta.
|
|
377
|
+
*/
|
|
378
|
+
function cmdActivar(argProyecto) {
|
|
379
|
+
const { proyecto, errorTexto } = _resolverOError(argProyecto);
|
|
380
|
+
if (errorTexto) return errorTexto;
|
|
381
|
+
|
|
382
|
+
eliminarMute(proyecto.nombre);
|
|
383
|
+
return `Proyecto <b>${escaparHtml(proyecto.nombre)}</b> activado. Volveras a recibir notificaciones.`;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Maneja /mutes.
|
|
388
|
+
*
|
|
389
|
+
* @returns {string} Lista de proyectos silenciados.
|
|
390
|
+
*/
|
|
391
|
+
function cmdMutes() {
|
|
392
|
+
// Leer directamente desde el módulo de config
|
|
393
|
+
const telegramConfig = require('../../hooks/lib/telegram-config');
|
|
394
|
+
const os = require('node:os');
|
|
395
|
+
const rutaMuted = path.join(os.homedir(), '.claude', 'notifications', 'muted.json');
|
|
396
|
+
|
|
397
|
+
let muted = [];
|
|
398
|
+
try {
|
|
399
|
+
if (fs.existsSync(rutaMuted)) {
|
|
400
|
+
const contenido = fs.readFileSync(rutaMuted, 'utf8');
|
|
401
|
+
const datos = JSON.parse(contenido);
|
|
402
|
+
muted = Array.isArray(datos) ? datos : [];
|
|
403
|
+
}
|
|
404
|
+
} catch (_) {
|
|
405
|
+
muted = [];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (muted.length === 0) {
|
|
409
|
+
return 'No hay proyectos silenciados.';
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const lista = muted.map((p) => `• ${escaparHtml(String(p))}`).join('\n');
|
|
413
|
+
return `<b>Proyectos silenciados (${muted.length}):</b>\n\n${lista}`;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Valida que el path de un proyecto resuelto no escapa del directorio de proyectos.
|
|
418
|
+
*
|
|
419
|
+
* Previene path traversal: el cwd del proyecto DEBE pertenecer a un proyecto
|
|
420
|
+
* conocido, y el subpath derivado DEBE comenzar con ese cwd.
|
|
421
|
+
*
|
|
422
|
+
* @param {string} cwdProyecto - cwd real del proyecto (desde descubrirProyectos).
|
|
423
|
+
* @param {string} subpath - Subpath a validar dentro del proyecto.
|
|
424
|
+
* @returns {boolean} true si es seguro, false si hay traversal.
|
|
425
|
+
*/
|
|
426
|
+
function _esCaminoSeguro(cwdProyecto, subpath) {
|
|
427
|
+
try {
|
|
428
|
+
const base = path.resolve(cwdProyecto);
|
|
429
|
+
const objetivo = path.resolve(subpath);
|
|
430
|
+
return objetivo.startsWith(base + path.sep) || objetivo === base;
|
|
431
|
+
} catch (_) {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Directorio de proyectos Claude Code (mismo que bot-discovery)
|
|
437
|
+
const DIR_PROYECTOS_CC = path.join(require('node:os').homedir(), '.claude', 'projects');
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Valida que el cwd de un proyecto conocido no fue manipulado para escapar
|
|
441
|
+
* del directorio de proyectos Claude Code.
|
|
442
|
+
*
|
|
443
|
+
* @param {string} cwd - cwd a verificar.
|
|
444
|
+
* @returns {boolean} true si es seguro.
|
|
445
|
+
*/
|
|
446
|
+
function _cwdEsSeguro(cwd) {
|
|
447
|
+
// El cwd debe ser un path real, sin intentar salir con ".."
|
|
448
|
+
// Comprobamos que al resolverlo no introduzca segmentos ".."
|
|
449
|
+
try {
|
|
450
|
+
const resuelto = path.resolve(cwd);
|
|
451
|
+
if (cwd.includes('..') || resuelto !== path.normalize(resuelto)) {
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// REM-5: blacklist de paths sensibles del sistema.
|
|
456
|
+
// Un proyecto Claude Code nunca debería tener cwd en estas rutas.
|
|
457
|
+
const homeDir = require('os').homedir();
|
|
458
|
+
const _pathsSensibles = [
|
|
459
|
+
path.join(homeDir, '.claude'),
|
|
460
|
+
path.join(homeDir, '.ssh'),
|
|
461
|
+
path.join(homeDir, '.aws'),
|
|
462
|
+
path.join(homeDir, '.gnupg'),
|
|
463
|
+
'/etc',
|
|
464
|
+
'/etc/shadow',
|
|
465
|
+
'/etc/passwd',
|
|
466
|
+
'C:\\Windows',
|
|
467
|
+
'C:\\Windows\\System32',
|
|
468
|
+
];
|
|
469
|
+
|
|
470
|
+
const resueltoNorm = resuelto.toLowerCase();
|
|
471
|
+
for (const sensible of _pathsSensibles) {
|
|
472
|
+
const sensibleNorm = sensible.toLowerCase();
|
|
473
|
+
if (resueltoNorm === sensibleNorm || resueltoNorm.startsWith(sensibleNorm + path.sep)) {
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return true;
|
|
479
|
+
} catch (_) {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// -----------------------------------------------------------------
|
|
485
|
+
// Lazy require de better-sqlite3 (dependencia opt-in)
|
|
486
|
+
// -----------------------------------------------------------------
|
|
487
|
+
|
|
488
|
+
let _dbModulo;
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Carga better-sqlite3 de forma lazy. Devuelve el constructor o null si
|
|
492
|
+
* no está instalado.
|
|
493
|
+
*
|
|
494
|
+
* @returns {Function|null} Constructor de better-sqlite3 o null.
|
|
495
|
+
*/
|
|
496
|
+
function _cargarDb() {
|
|
497
|
+
if (_dbModulo === undefined) {
|
|
498
|
+
try {
|
|
499
|
+
// La carga se hace con require() dinámico para permitir que el módulo
|
|
500
|
+
// funcione incluso cuando better-sqlite3 no está instalado (opt-in).
|
|
501
|
+
// eslint-disable-next-line global-require
|
|
502
|
+
_dbModulo = require('better-sqlite3');
|
|
503
|
+
} catch (_) {
|
|
504
|
+
_dbModulo = null;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return _dbModulo;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// -----------------------------------------------------------------
|
|
511
|
+
// Handler /inbox
|
|
512
|
+
// -----------------------------------------------------------------
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Maneja /inbox <proyecto>.
|
|
516
|
+
*
|
|
517
|
+
* Lee <proyecto>/.planning/inbox/cmd-*.json, agrupa pendientes y procesados,
|
|
518
|
+
* devuelve top 10 pendientes con preview de 120 chars del campo `comando`
|
|
519
|
+
* o `mensaje`.
|
|
520
|
+
*
|
|
521
|
+
* @param {string} argProyecto - Término de búsqueda del proyecto.
|
|
522
|
+
* @returns {string} Texto de respuesta HTML.
|
|
523
|
+
*/
|
|
524
|
+
function cmdInbox(argProyecto) {
|
|
525
|
+
const { proyecto, errorTexto } = _resolverOError(argProyecto);
|
|
526
|
+
if (errorTexto) return errorTexto;
|
|
527
|
+
|
|
528
|
+
if (!_cwdEsSeguro(proyecto.cwd)) {
|
|
529
|
+
console.error(`[bot] cmdInbox: path traversal rechazado para cwd="${proyecto.cwd}"`);
|
|
530
|
+
return 'path traversal rechazado';
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const dirInbox = path.join(proyecto.cwd, '.planning', 'inbox');
|
|
534
|
+
|
|
535
|
+
try {
|
|
536
|
+
if (!fs.existsSync(dirInbox)) {
|
|
537
|
+
return `<b>${escaparHtml(proyecto.nombre)}</b> no tiene inbox todavía.`;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
let entradas;
|
|
541
|
+
try {
|
|
542
|
+
entradas = fs.readdirSync(dirInbox);
|
|
543
|
+
} catch (_) {
|
|
544
|
+
return `Error al leer inbox de <b>${escaparHtml(proyecto.nombre)}</b>.`;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Filtrar archivos cmd-*.json
|
|
548
|
+
const archivosCmds = entradas.filter(
|
|
549
|
+
(e) => e.startsWith('cmd-') && e.endsWith('.json')
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
if (archivosCmds.length === 0) {
|
|
553
|
+
return `<b>${escaparHtml(proyecto.nombre)}</b> sin comandos pendientes.`;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Separar pendientes (sin .processed.json) y procesados
|
|
557
|
+
const pendientes = archivosCmds.filter((a) => !a.endsWith('.processed.json'));
|
|
558
|
+
const procesados = archivosCmds.filter((a) => a.endsWith('.processed.json'));
|
|
559
|
+
|
|
560
|
+
if (pendientes.length === 0) {
|
|
561
|
+
return (
|
|
562
|
+
`<b>${escaparHtml(proyecto.nombre)}</b> sin comandos pendientes.\n` +
|
|
563
|
+
`(${procesados.length} procesados)`
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Top 10 pendientes, ordenados por nombre (que incluye timestamp)
|
|
568
|
+
const top10 = pendientes.sort().slice(0, 10);
|
|
569
|
+
|
|
570
|
+
const lineas = top10.map((archivo) => {
|
|
571
|
+
const ruta = path.join(dirInbox, archivo);
|
|
572
|
+
let preview = archivo;
|
|
573
|
+
try {
|
|
574
|
+
// Validar path: debe estar dentro del dirInbox
|
|
575
|
+
const rutaResuelta = path.resolve(ruta);
|
|
576
|
+
const baseResuelta = path.resolve(dirInbox);
|
|
577
|
+
if (!rutaResuelta.startsWith(baseResuelta + path.sep) && rutaResuelta !== baseResuelta) {
|
|
578
|
+
return `• <code>${escaparHtml(archivo)}</code> (path inválido)`;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const contenido = fs.readFileSync(ruta, 'utf8');
|
|
582
|
+
const obj = JSON.parse(contenido);
|
|
583
|
+
// Intentar campo `comando` o `mensaje`
|
|
584
|
+
const texto = String(obj.comando || obj.mensaje || JSON.stringify(obj) || '');
|
|
585
|
+
// Preview de 120 chars
|
|
586
|
+
preview = texto.length > 120 ? texto.slice(0, 120) + '…' : texto;
|
|
587
|
+
} catch (_) {
|
|
588
|
+
preview = '(sin parsear)';
|
|
589
|
+
}
|
|
590
|
+
return `• <code>${escaparHtml(archivo)}</code>\n ${escaparHtml(preview)}`;
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
const encabezado =
|
|
594
|
+
`<b>Inbox de ${escaparHtml(proyecto.nombre)}:</b> ` +
|
|
595
|
+
`${pendientes.length} pendiente(s), ${procesados.length} procesado(s)`;
|
|
596
|
+
|
|
597
|
+
return truncarRespuesta(encabezado + '\n\n' + lineas.join('\n\n'));
|
|
598
|
+
|
|
599
|
+
} catch (err) {
|
|
600
|
+
console.error(`[bot] cmdInbox error: ${err && err.message}`);
|
|
601
|
+
return `Error al consultar inbox de <b>${escaparHtml(proyecto.nombre)}</b>.`;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// -----------------------------------------------------------------
|
|
606
|
+
// Handler /costo
|
|
607
|
+
// -----------------------------------------------------------------
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Maneja /costo [d].
|
|
611
|
+
*
|
|
612
|
+
* Lee ~/.claude/usage.db (SQLite con better-sqlite3) y devuelve tokens y USD
|
|
613
|
+
* del día actual o de los últimos d días.
|
|
614
|
+
*
|
|
615
|
+
* Nota: requiere `better-sqlite3` instalado manualmente en `bin/`:
|
|
616
|
+
* cd bin && npm install --save better-sqlite3
|
|
617
|
+
* Si no está instalado, devuelve mensaje explicativo sin crashear.
|
|
618
|
+
*
|
|
619
|
+
* @param {string} argDias - Número de días (opcional, default 1).
|
|
620
|
+
* @returns {string} Texto de respuesta HTML.
|
|
621
|
+
*/
|
|
622
|
+
function cmdCosto(argDias) {
|
|
623
|
+
const BetterSqlite3 = _cargarDb();
|
|
624
|
+
if (!BetterSqlite3) {
|
|
625
|
+
return (
|
|
626
|
+
'instalación incompleta: falta better-sqlite3\n' +
|
|
627
|
+
'<code>npm install --save better-sqlite3</code> en <code>bin/</code>'
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const os = require('node:os');
|
|
632
|
+
const rutaDb = path.join(os.homedir(), '.claude', 'usage.db');
|
|
633
|
+
|
|
634
|
+
if (!fs.existsSync(rutaDb)) {
|
|
635
|
+
return 'no hay datos de uso registrados todavía.';
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Clamp silencioso: máximo 90 días
|
|
639
|
+
const diasRaw = parseInt(argDias, 10);
|
|
640
|
+
const dias = isNaN(diasRaw) || diasRaw < 1 ? 1 : Math.min(diasRaw, 90);
|
|
641
|
+
|
|
642
|
+
let db;
|
|
643
|
+
try {
|
|
644
|
+
db = new BetterSqlite3(rutaDb, { readonly: true });
|
|
645
|
+
|
|
646
|
+
// Inspeccionar schema: obtener columnas de la tabla usage
|
|
647
|
+
const infoTabla = db.prepare("PRAGMA table_info('usage')").all();
|
|
648
|
+
const columnas = new Set(infoTabla.map((c) => c.name));
|
|
649
|
+
|
|
650
|
+
if (columnas.size === 0) {
|
|
651
|
+
db.close();
|
|
652
|
+
return 'La base de datos usage.db no tiene la tabla <code>usage</code>.';
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Construir SELECT dinámico solo con columnas existentes
|
|
656
|
+
// (el schema puede variar entre versiones de Claude Code)
|
|
657
|
+
const colInput = columnas.has('input_tokens') ? 'input_tokens' : null;
|
|
658
|
+
const colOutput = columnas.has('output_tokens') ? 'output_tokens' : null;
|
|
659
|
+
const colCacheCreate = columnas.has('cache_creation_tokens') ? 'cache_creation_tokens' : null;
|
|
660
|
+
const colCacheRead = columnas.has('cache_read_tokens') ? 'cache_read_tokens' : null;
|
|
661
|
+
const colCost = columnas.has('cost_usd') ? 'cost_usd' : null;
|
|
662
|
+
const colModel = columnas.has('model') ? 'model' : null;
|
|
663
|
+
const colTimestamp = columnas.has('timestamp') ? 'timestamp' : null;
|
|
664
|
+
|
|
665
|
+
if (!colTimestamp) {
|
|
666
|
+
db.close();
|
|
667
|
+
return 'La tabla <code>usage</code> no tiene columna <code>timestamp</code> — schema inesperado.';
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Calcular límite de tiempo: hace N días en unix epoch (segundos o milisegundos)
|
|
671
|
+
// Claude Code guarda el timestamp como ISO 8601 o epoch entero — probar ambos
|
|
672
|
+
const ahora = Date.now();
|
|
673
|
+
const limiteMs = ahora - dias * 24 * 60 * 60 * 1000;
|
|
674
|
+
const limiteSeg = Math.floor(limiteMs / 1000);
|
|
675
|
+
|
|
676
|
+
// Probar formato del timestamp con la primera fila
|
|
677
|
+
const primeraFila = db.prepare(`SELECT ${colTimestamp} AS ts FROM usage LIMIT 1`).get();
|
|
678
|
+
if (!primeraFila) {
|
|
679
|
+
db.close();
|
|
680
|
+
return `Sin registros de uso en los últimos ${dias} día(s).`;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const tsVal = primeraFila.ts;
|
|
684
|
+
// REM-4: usar bind parameters de SQLite en lugar de interpolación de valores numéricos.
|
|
685
|
+
// Aunque los valores son internos (Date.now(), no input de usuario), bind params
|
|
686
|
+
// es la práctica correcta para prevenir inyección si el código evolucionara.
|
|
687
|
+
let whereClause;
|
|
688
|
+
let bindParam;
|
|
689
|
+
if (typeof tsVal === 'number' && tsVal > 1_000_000_000_000) {
|
|
690
|
+
// Epoch en milisegundos
|
|
691
|
+
whereClause = `WHERE ${colTimestamp} >= ?`;
|
|
692
|
+
bindParam = limiteMs;
|
|
693
|
+
} else if (typeof tsVal === 'number') {
|
|
694
|
+
// Epoch en segundos
|
|
695
|
+
whereClause = `WHERE ${colTimestamp} >= ?`;
|
|
696
|
+
bindParam = limiteSeg;
|
|
697
|
+
} else {
|
|
698
|
+
// ISO 8601 string — usar comparación de string (funciona para fechas ISO)
|
|
699
|
+
const fechaIso = new Date(limiteMs).toISOString().slice(0, 10);
|
|
700
|
+
whereClause = `WHERE ${colTimestamp} >= ?`;
|
|
701
|
+
bindParam = fechaIso;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Query agregada con bind parameters (sin interpolación de valores)
|
|
705
|
+
const partesSel = [];
|
|
706
|
+
if (colInput) partesSel.push(`SUM(${colInput}) AS total_input`);
|
|
707
|
+
if (colOutput) partesSel.push(`SUM(${colOutput}) AS total_output`);
|
|
708
|
+
if (colCacheCreate) partesSel.push(`SUM(${colCacheCreate}) AS total_cache_create`);
|
|
709
|
+
if (colCacheRead) partesSel.push(`SUM(${colCacheRead}) AS total_cache_read`);
|
|
710
|
+
if (colCost) partesSel.push(`SUM(${colCost}) AS total_costo`);
|
|
711
|
+
partesSel.push('COUNT(*) AS total_llamadas');
|
|
712
|
+
|
|
713
|
+
const queryTotal = db.prepare(`SELECT ${partesSel.join(', ')} FROM usage ${whereClause}`);
|
|
714
|
+
const totales = queryTotal.get(bindParam);
|
|
715
|
+
|
|
716
|
+
if (!totales || totales.total_llamadas === 0) {
|
|
717
|
+
db.close();
|
|
718
|
+
return `Sin registros de uso en los últimos ${dias} día(s).`;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Breakdown por modelo si hay más de uno
|
|
722
|
+
let breakdownHtml = '';
|
|
723
|
+
if (colModel) {
|
|
724
|
+
const partesMod = [];
|
|
725
|
+
if (colInput) partesMod.push(`SUM(${colInput}) AS tot_in`);
|
|
726
|
+
if (colOutput) partesMod.push(`SUM(${colOutput}) AS tot_out`);
|
|
727
|
+
if (colCost) partesMod.push(`SUM(${colCost}) AS tot_costo`);
|
|
728
|
+
partesMod.push(`${colModel} AS modelo`, 'COUNT(*) AS llamadas');
|
|
729
|
+
|
|
730
|
+
const queryMod = db.prepare(
|
|
731
|
+
`SELECT ${partesMod.join(', ')} FROM usage ${whereClause} GROUP BY ${colModel} ORDER BY tot_costo DESC`
|
|
732
|
+
);
|
|
733
|
+
const filasMod = queryMod.all(bindParam);
|
|
734
|
+
|
|
735
|
+
if (filasMod.length > 1) {
|
|
736
|
+
const lineasMod = filasMod.map((f) => {
|
|
737
|
+
const costoMod = colCost ? ` · $${Number(f.tot_costo || 0).toFixed(4)}` : '';
|
|
738
|
+
const inOut = (colInput && colOutput)
|
|
739
|
+
? ` (${_fmtTokens(f.tot_in)}/${_fmtTokens(f.tot_out)})`
|
|
740
|
+
: '';
|
|
741
|
+
return ` • <code>${escaparHtml(String(f.modelo || '?'))}</code>${inOut}${costoMod} · ${f.llamadas} llamadas`;
|
|
742
|
+
});
|
|
743
|
+
breakdownHtml = '\n\n<b>Por modelo:</b>\n' + lineasMod.join('\n');
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
db.close();
|
|
748
|
+
|
|
749
|
+
// Construir respuesta — no incluye USD en log (solo en respuesta a Telegram)
|
|
750
|
+
const titulo = dias === 1
|
|
751
|
+
? '<b>Uso de hoy:</b>'
|
|
752
|
+
: `<b>Uso de los últimos ${dias} días:</b>`;
|
|
753
|
+
|
|
754
|
+
const lineas = [];
|
|
755
|
+
if (colInput) lineas.push(`Tokens entrada: ${_fmtTokens(totales.total_input)}`);
|
|
756
|
+
if (colOutput) lineas.push(`Tokens salida: ${_fmtTokens(totales.total_output)}`);
|
|
757
|
+
if (colCacheCreate) lineas.push(`Cache creación: ${_fmtTokens(totales.total_cache_create)}`);
|
|
758
|
+
if (colCacheRead) lineas.push(`Cache lectura: ${_fmtTokens(totales.total_cache_read)}`);
|
|
759
|
+
if (colCost) lineas.push(`Costo total USD: $${Number(totales.total_costo || 0).toFixed(4)}`);
|
|
760
|
+
lineas.push(`Llamadas: ${totales.total_llamadas}`);
|
|
761
|
+
|
|
762
|
+
return truncarRespuesta(
|
|
763
|
+
titulo + '\n\n<pre>' + lineas.join('\n') + '</pre>' + breakdownHtml
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
} catch (err) {
|
|
767
|
+
if (db) { try { db.close(); } catch (_) {} }
|
|
768
|
+
console.error(`[bot] cmdCosto error: ${err && err.message}`);
|
|
769
|
+
return `Error al leer datos de uso: <code>${escaparHtml(String(err && err.message || err))}</code>`;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Formatea un número de tokens con separadores de miles.
|
|
775
|
+
*
|
|
776
|
+
* @param {number|null|undefined} n - Número de tokens.
|
|
777
|
+
* @returns {string} Número formateado.
|
|
778
|
+
*/
|
|
779
|
+
function _fmtTokens(n) {
|
|
780
|
+
const num = Number(n) || 0;
|
|
781
|
+
return num.toLocaleString('es-MX');
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// -----------------------------------------------------------------
|
|
785
|
+
// Handler /salud
|
|
786
|
+
// -----------------------------------------------------------------
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Maneja /salud <proyecto>.
|
|
790
|
+
*
|
|
791
|
+
* Busca SALUD.md en <p>/ o <p>/.planning/ y extrae el score y las
|
|
792
|
+
* primeras 3 advertencias encontradas.
|
|
793
|
+
*
|
|
794
|
+
* @param {string} argProyecto - Término de búsqueda del proyecto.
|
|
795
|
+
* @returns {string} Texto de respuesta HTML.
|
|
796
|
+
*/
|
|
797
|
+
function cmdSalud(argProyecto) {
|
|
798
|
+
const { proyecto, errorTexto } = _resolverOError(argProyecto);
|
|
799
|
+
if (errorTexto) return errorTexto;
|
|
800
|
+
|
|
801
|
+
if (!_cwdEsSeguro(proyecto.cwd)) {
|
|
802
|
+
console.error(`[bot] cmdSalud: path traversal rechazado para cwd="${proyecto.cwd}"`);
|
|
803
|
+
return 'path traversal rechazado';
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const candidatos = [
|
|
807
|
+
path.join(proyecto.cwd, 'SALUD.md'),
|
|
808
|
+
path.join(proyecto.cwd, '.planning', 'SALUD.md'),
|
|
809
|
+
];
|
|
810
|
+
|
|
811
|
+
for (const ruta of candidatos) {
|
|
812
|
+
// Validar que el path candidato no escapa del cwd del proyecto
|
|
813
|
+
if (!_esCaminoSeguro(proyecto.cwd, ruta)) {
|
|
814
|
+
console.error(`[bot] cmdSalud: path traversal rechazado: "${ruta}"`);
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
try {
|
|
819
|
+
if (!fs.existsSync(ruta)) continue;
|
|
820
|
+
const contenido = fs.readFileSync(ruta, 'utf8');
|
|
821
|
+
if (!contenido.trim()) continue;
|
|
822
|
+
|
|
823
|
+
return truncarRespuesta(_parsearSalud(proyecto.nombre, contenido));
|
|
824
|
+
} catch (_) {
|
|
825
|
+
// Siguiente candidato
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
return `<b>${escaparHtml(proyecto.nombre)}</b> no tiene SALUD.md (correr /swl:salud)`;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* Parsea el contenido de un SALUD.md y devuelve HTML con score y advertencias.
|
|
834
|
+
*
|
|
835
|
+
* @param {string} nombreProyecto - Nombre del proyecto para el encabezado.
|
|
836
|
+
* @param {string} contenido - Contenido completo del SALUD.md.
|
|
837
|
+
* @returns {string} HTML con score y top 3 advertencias.
|
|
838
|
+
*/
|
|
839
|
+
function _parsearSalud(nombreProyecto, contenido) {
|
|
840
|
+
// Extraer score — patrones conocidos:
|
|
841
|
+
// "Score Global: 100% — ..."
|
|
842
|
+
// "Score: 95/100"
|
|
843
|
+
// "## Score Global: 100%"
|
|
844
|
+
const reScore = /score\s*(?:global)?[:\s]+(\d+(?:\.\d+)?)\s*(?:%|\/\s*100)/i;
|
|
845
|
+
const matchScore = contenido.match(reScore);
|
|
846
|
+
const scoreTexto = matchScore ? matchScore[0].trim() : null;
|
|
847
|
+
|
|
848
|
+
// Extraer advertencias: líneas que contengan palabras clave de problema
|
|
849
|
+
// (buscamos en filas de tabla, listas o líneas con palabras indicativas)
|
|
850
|
+
const lineas = contenido.split('\n');
|
|
851
|
+
const advertencias = [];
|
|
852
|
+
|
|
853
|
+
for (const linea of lineas) {
|
|
854
|
+
const l = linea.trim();
|
|
855
|
+
if (!l) continue;
|
|
856
|
+
|
|
857
|
+
// Buscar filas de tabla con problemas (columna Problemas > 0)
|
|
858
|
+
// Formato: | Componente | Score | Estado | Problemas |
|
|
859
|
+
// o líneas con estados de advertencia/error
|
|
860
|
+
if (
|
|
861
|
+
/\|\s*\d+\s*\|/.test(l) && // tiene número en tabla
|
|
862
|
+
/[1-9]\d*/.test(l) && // hay al menos un dígito no-cero
|
|
863
|
+
!/\|\s*0\s*\|/.test(l.replace(/(\d+%)/, '')) // no es solo ceros
|
|
864
|
+
) {
|
|
865
|
+
const columnas = l.split('|').map((c) => c.trim()).filter(Boolean);
|
|
866
|
+
// La última columna suele ser el count de problemas
|
|
867
|
+
const ultimo = columnas[columnas.length - 1];
|
|
868
|
+
if (ultimo && /^[1-9]\d*$/.test(ultimo)) {
|
|
869
|
+
advertencias.push(l.replace(/\|/g, ' ').replace(/\s+/g, ' ').trim());
|
|
870
|
+
if (advertencias.length >= 3) break;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Líneas de sección "Problemas críticos" o "Advertencias"
|
|
875
|
+
if (advertencias.length < 3 && /(?:problem|advertencia|error|crítico|warning)/i.test(l)) {
|
|
876
|
+
// Solo incluir si parece contenido real (no solo el encabezado de sección)
|
|
877
|
+
if (l.length > 10 && !l.startsWith('#')) {
|
|
878
|
+
advertencias.push(l);
|
|
879
|
+
if (advertencias.length >= 3) break;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const encabezado = `<b>Salud de ${escaparHtml(nombreProyecto)}</b>`;
|
|
885
|
+
const scoreHtml = scoreTexto
|
|
886
|
+
? `\n<b>Score:</b> ${escaparHtml(scoreTexto)}`
|
|
887
|
+
: '\n<i>(score no encontrado)</i>';
|
|
888
|
+
|
|
889
|
+
let advertenciasHtml = '';
|
|
890
|
+
if (advertencias.length > 0) {
|
|
891
|
+
const lista = advertencias
|
|
892
|
+
.slice(0, 3)
|
|
893
|
+
.map((a) => `• ${escaparHtml(a)}`)
|
|
894
|
+
.join('\n');
|
|
895
|
+
advertenciasHtml = '\n\n<b>Advertencias (top 3):</b>\n' + lista;
|
|
896
|
+
} else {
|
|
897
|
+
advertenciasHtml = '\n\n<i>Sin advertencias detectadas.</i>';
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
return encabezado + scoreHtml + advertenciasHtml;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* Maneja /quien.
|
|
905
|
+
*
|
|
906
|
+
* @param {string|number} chatId - ID del chat del remitente.
|
|
907
|
+
* @returns {string} Texto con el chat_id.
|
|
908
|
+
*/
|
|
909
|
+
function cmdQuien(chatId) {
|
|
910
|
+
return `Tu chat_id es: <code>${escaparHtml(String(chatId))}</code>`;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
/**
|
|
914
|
+
* Despacha un mensaje de Telegram al handler correspondiente.
|
|
915
|
+
*
|
|
916
|
+
* @param {object} mensaje - Objeto mensaje de Telegram.
|
|
917
|
+
* @param {string} ownerChatId - Chat ID autorizado (como string).
|
|
918
|
+
* @returns {{ respuesta: string, rechazado: boolean }}
|
|
919
|
+
*/
|
|
920
|
+
function despachar(mensaje, ownerChatId) {
|
|
921
|
+
const chatId = String(mensaje?.chat?.id || '');
|
|
922
|
+
const texto = String(mensaje?.text || '').trim();
|
|
923
|
+
const ownerStr = String(ownerChatId || '');
|
|
924
|
+
|
|
925
|
+
// Filtro de acceso
|
|
926
|
+
if (chatId !== ownerStr) {
|
|
927
|
+
return { respuesta: 'Acceso denegado', rechazado: true };
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (!texto.startsWith('/')) {
|
|
931
|
+
// Mensaje sin comando: ignorar silenciosamente
|
|
932
|
+
return { respuesta: '', rechazado: false };
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const { comando, argProyecto, argExtra } = parsearMensaje(texto);
|
|
936
|
+
|
|
937
|
+
let respuesta = '';
|
|
938
|
+
|
|
939
|
+
switch (comando) {
|
|
940
|
+
case '/start':
|
|
941
|
+
case '/help':
|
|
942
|
+
respuesta = cmdHelp();
|
|
943
|
+
break;
|
|
944
|
+
|
|
945
|
+
case '/proyectos':
|
|
946
|
+
respuesta = cmdProyectos();
|
|
947
|
+
break;
|
|
948
|
+
|
|
949
|
+
case '/estado':
|
|
950
|
+
respuesta = cmdEstado(argProyecto);
|
|
951
|
+
break;
|
|
952
|
+
|
|
953
|
+
case '/log':
|
|
954
|
+
respuesta = cmdLog(argProyecto, argExtra);
|
|
955
|
+
break;
|
|
956
|
+
|
|
957
|
+
case '/diff':
|
|
958
|
+
respuesta = cmdDiff(argProyecto);
|
|
959
|
+
break;
|
|
960
|
+
|
|
961
|
+
case '/ultimo':
|
|
962
|
+
respuesta = cmdUltimo(argProyecto);
|
|
963
|
+
break;
|
|
964
|
+
|
|
965
|
+
case '/resumen':
|
|
966
|
+
respuesta = cmdResumen(argProyecto);
|
|
967
|
+
break;
|
|
968
|
+
|
|
969
|
+
case '/inbox':
|
|
970
|
+
respuesta = cmdInbox(argProyecto);
|
|
971
|
+
break;
|
|
972
|
+
|
|
973
|
+
case '/costo':
|
|
974
|
+
// argProyecto realmente es el número de días cuando se usa /costo [d]
|
|
975
|
+
respuesta = cmdCosto(argProyecto);
|
|
976
|
+
break;
|
|
977
|
+
|
|
978
|
+
case '/salud':
|
|
979
|
+
respuesta = cmdSalud(argProyecto);
|
|
980
|
+
break;
|
|
981
|
+
|
|
982
|
+
case '/silencio':
|
|
983
|
+
respuesta = cmdSilencio(argProyecto);
|
|
984
|
+
break;
|
|
985
|
+
|
|
986
|
+
case '/activar':
|
|
987
|
+
respuesta = cmdActivar(argProyecto);
|
|
988
|
+
break;
|
|
989
|
+
|
|
990
|
+
case '/mutes':
|
|
991
|
+
respuesta = cmdMutes();
|
|
992
|
+
break;
|
|
993
|
+
|
|
994
|
+
case '/quien':
|
|
995
|
+
respuesta = cmdQuien(chatId);
|
|
996
|
+
break;
|
|
997
|
+
|
|
998
|
+
default:
|
|
999
|
+
respuesta = `Comando no reconocido: <code>${escaparHtml(comando)}</code>\n\nEscribe /help para ver los comandos disponibles.`;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
return { respuesta, rechazado: false };
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
module.exports = {
|
|
1006
|
+
despachar,
|
|
1007
|
+
parsearMensaje,
|
|
1008
|
+
truncarRespuesta,
|
|
1009
|
+
escaparHtml,
|
|
1010
|
+
cmdHelp,
|
|
1011
|
+
cmdProyectos,
|
|
1012
|
+
cmdEstado,
|
|
1013
|
+
cmdLog,
|
|
1014
|
+
cmdDiff,
|
|
1015
|
+
cmdUltimo,
|
|
1016
|
+
cmdResumen,
|
|
1017
|
+
cmdInbox,
|
|
1018
|
+
cmdCosto,
|
|
1019
|
+
cmdSalud,
|
|
1020
|
+
cmdSilencio,
|
|
1021
|
+
cmdActivar,
|
|
1022
|
+
cmdMutes,
|
|
1023
|
+
cmdQuien,
|
|
1024
|
+
LIMITE_RESPUESTA,
|
|
1025
|
+
// Exportar para tests
|
|
1026
|
+
_parsearSalud,
|
|
1027
|
+
_esCaminoSeguro,
|
|
1028
|
+
_cwdEsSeguro,
|
|
1029
|
+
_cargarDb,
|
|
1030
|
+
};
|