opencode-pollinations-plugin 6.4.6 → 6.4.7

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.
@@ -116,7 +116,7 @@
116
116
  "infos": {
117
117
  "title": "## 🍯💚 POLLINATIONS OPENCODE PLUGIN 💚🍯\n\nWillkommen **{name}** beim Pollinations OpenCode Plugin!\n\nDieses Plugin ermöglicht es, Code und Bilder zu generieren, Videos zu analysieren und nahtlos mit den besten KI-Modellen zu interagieren. Greifen Sie über Chat, Refactoring oder Terminal auf erstklassige LLMs zu.",
118
118
  "features_title": "**Was dieses Plugin Ihnen bietet! :**",
119
- "features_free": "**🛠️ Kostenlose Tools (Immer verfugbar):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Visuals und Dev-Tools.\n- `remove_background` : Hintergrundentfernung.\n- `extract_frames` / `extract_audio` : Medien-Extraktion.\n- `file_to_url` : Online-Hosting.\n- `gen_edit_image_free` : Kostenlose Bild-Generierung & Bearbeitung (~20/Tag).\n- `gen_video_free` : Kostenlose Video-Generierung (~5/Tag).",
119
+ "features_free": "**🛠️ Kostenlose Tools (Immer verfugbar):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Visuals und Dev-Tools.\n- `remove_background` : Hintergrundentfernung.\n- `extract_frames` / `extract_audio` : Medien-Extraktion.\n- `file_to_url` : Online-Hosting.\n- `gen_edit_image_free` : Kostenlose Bild-Generierung & Bearbeitung (~20/Tag).\n- `gen_video_free` : Kostenlose Video-Generierung (~5/Tag).\n- `object_remover` : Suppression d’objets par prompt (gratuit, direct).\n- `image_upscaler` : Agrandissement 2x/4x (gratuit, direct).\n- `image_enhancer` : Amélioration IA d’image (gratuit, direct).",
120
120
  "features_pro": "**💎 Pollinations Tools (Premium - Automatisiert mit Key):**\n- `polli_gen_image` : Bildgen (Flux, Gemini) + Image-to-Image.\n- `polli_gen_video` : Text-to-Video / Image-to-Video (Veo, Wan...).\n- `polli_gen_audio`/`polli_stt` : Whisper STT, ElevenLabs TTS.\n- `polli_gen_music` : Generative Musik-Engine.\n- `polli_web_search` : Verbundene Websuche.",
121
121
  "features_config": "- **Granulare Konfiguration**, Modus- und Token-Management, Kostenschutz...",
122
122
  "tiers_title": "> **Dein Quest Pollen Refill:** {emoji} {tier}",
@@ -445,11 +445,26 @@
445
445
  "polli_login": {
446
446
  "desc": "🔐 Startet einen schnellen Pollinations-Login (Device Flow) für den Benutzer. Zeigt einen kurzen Code + URL; der Benutzer meldet sich im Browser mit GitHub an, und das Plugin verbindet sich AUTOMATISCH nach Genehmigung (ein Toast bestätigt). Kein API-Schlüssel zum Ausführen nötig. Verwende dies, wenn der Benutzer sein Pollinations-Konto einfach verbinden möchte, ohne einen Schlüssel zu kopieren. Für einen permanenten Schlüssel sage ihm, `/poll connect sk_...` zu verwenden.",
447
447
  "arg_wait": "Wenn true, wartet auf den Abschluss eines laufenden Logins und gibt das Endergebnis zurück (✅ verbunden / abgelaufen). Rufe zuerst ohne wait auf, um den Code anzuzeigen, dann erneut mit wait:true, nachdem du den Benutzer zur Autorisierung aufgefordert hast."
448
+ },
449
+ "object_remover": {
450
+ "file_not_found": "❗ File not found: {path}",
451
+ "no_result": "❗ No result received.",
452
+ "error": "❗ Error: {error}"
453
+ },
454
+ "image_upscaler": {
455
+ "file_not_found": "❗ File not found: {path}",
456
+ "no_result": "❗ No result received.",
457
+ "error": "❗ Error: {error}"
458
+ },
459
+ "image_enhancer": {
460
+ "file_not_found": "❗ File not found: {path}",
461
+ "no_result": "❗ No result received.",
462
+ "error": "❗ Error: {error}"
448
463
  }
449
464
  },
450
465
  "connect_response": {
451
466
  "title_key": "## 🍯💚 POLLINATIONS PLUGIN VERBUNDEN 💚🍯\n\nWillkommen **{name}** beim Pollinations Agent Plugin!\n**Aktueller Modus**: `{mode}`",
452
- "tools_intro": "### 🚀 Multimedia Tools\n\nDieses Plugin ermöglicht es, Code und Bilder zu generieren, Videos zu analysieren und mit den besten KI-Modellen zu interagieren.\n\n**🛠️ Kostenlose Tools:**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (Bild-Gen & Edit, ~20/Tag)\n- `gen_video_free` (Video-Gen, ~5/Tag)\n\n**💎 Premium Tools:**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
467
+ "tools_intro": "### 🚀 Multimedia Tools\n\nDieses Plugin ermöglicht es, Code und Bilder zu generieren, Videos zu analysieren und mit den besten KI-Modellen zu interagieren.\n\n**🛠️ Kostenlose Tools:**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (Bild-Gen & Edit, ~20/Tag)\n- `gen_video_free` (Video-Gen, ~5/Tag)\n- `object_remover`\n- `image_upscaler`\n- `image_enhancer`\n\n**💎 Premium Tools:**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
453
468
  "terminal_cmds": "### 💻 Terminal Befehle\nVerwenden Sie `/poll help` um alle Befehle anzuzeigen.",
454
469
  "resources": "### 📚 Ressourcen\n* **Dashboard:** enter.pollinations.ai\n* **Discord:** Community beitreten\n* **Plugin:** github.com/fkom13/opencode-pollinations-plugin",
455
470
  "free_models_success": "Verfügbare Modelle: {models}",
@@ -115,7 +115,7 @@
115
115
  "infos": {
116
116
  "title": "## 🍯💚 POLLINATIONS OPENCODE PLUGIN 💚🍯\n\nWelcome **{name}** to the Pollinations OpenCode plugin!\n\nThis plugin empowers you to generate code, images, analyze videos, and interact with top-tier AI models seamlessly within your dev environment. Access state-of-the-art LLMs via chat, refactoring, or terminal.",
117
117
  "features_title": "**What this plugin brings to you! :**",
118
- "features_free": "**🛠️ Integrated Free Tools (Always Available):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Visuals and dev utilities.\n- `remove_background` : Native background removal.\n- `extract_frames` / `extract_audio` : Fast media extraction.\n- `file_to_url` : Instant online hosting of local files.\n- `gen_edit_image_free` : Free image gen & edit (~20/day).\n- `gen_video_free` : Free video gen (~5/day).",
118
+ "features_free": "**🛠️ Integrated Free Tools (Always Available):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Visuals and dev utilities.\n- `remove_background` : Native background removal.\n- `extract_frames` / `extract_audio` : Fast media extraction.\n- `file_to_url` : Instant online hosting of local files.\n- `gen_edit_image_free` : Free image gen & edit (~20/day).\n- `gen_video_free` : Free video gen (~5/day).\n- `object_remover` : Suppression d’objets par prompt (gratuit, direct).\n- `image_upscaler` : Agrandissement 2x/4x (gratuit, direct).\n- `image_enhancer` : Amélioration IA d’image (gratuit, direct).",
119
119
  "features_pro": "**💎 Pollinations Tools (Premium - Automated with Key) :**\n- `polli_gen_image` : Image gen (Flux, Seedream, Gemini) + Image-to-Image.\n- `polli_gen_video` : Text-to-video / Image-to-video (Veo, Wan, LTX...).\n- `polli_gen_audio`/`polli_stt` : Whisper STT, ElevenLabs TTS.\n- `polli_gen_music` : Generative music engine.\n- `polli_web_search` : Connected web search.",
120
120
  "features_config": "- **Granular configuration**, mode and token management, tool cost protections...",
121
121
  "tiers_title": "> **Your Quest Pollen refill:** {emoji} {tier}",
@@ -444,11 +444,26 @@
444
444
  "polli_login": {
445
445
  "desc": "🔐 Start a quick Pollinations login (device flow) for the user. Shows a short code + URL; the user signs in with GitHub in their browser, and the plugin connects AUTOMATICALLY once approved (a toast confirms). No API key needed to run this. Use when the user wants to connect their Pollinations account easily, without copy-pasting a key. For a permanent key instead, tell them to use `/poll connect sk_...`.",
446
446
  "arg_wait": "If true, waits for an in-progress login to complete and returns the final result (✅ connected / expired). Call once without wait to show the code, then again with wait:true after telling the user to authorize."
447
+ },
448
+ "object_remover": {
449
+ "file_not_found": "❗ File not found: {path}",
450
+ "no_result": "❗ No result received.",
451
+ "error": "❗ Error: {error}"
452
+ },
453
+ "image_upscaler": {
454
+ "file_not_found": "❗ File not found: {path}",
455
+ "no_result": "❗ No result received.",
456
+ "error": "❗ Error: {error}"
457
+ },
458
+ "image_enhancer": {
459
+ "file_not_found": "❗ File not found: {path}",
460
+ "no_result": "❗ No result received.",
461
+ "error": "❗ Error: {error}"
447
462
  }
448
463
  },
449
464
  "connect_response": {
450
465
  "title_key": "## 🍯💚 POLLINATIONS PLUGIN CONNECTED 💚🍯\n\nWelcome **{name}** to the Pollinations Agent plugin!\n**Current Mode**: `{mode}`",
451
- "tools_intro": "### 🚀 Multimedia Tools Integrated to Development\n\nThis plugin allows you to generate code, images, analyze videos and interact with the best AI models seamlessly. Access state-of-the-art LLMs via chat, refactoring, or terminal.\n\n**What this plugin brings to you:**\n\n**🛠️ Free Integrated Tools (Always Available):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (image gen & edit, ~20/day)\n- `gen_video_free` (video gen, ~5/day)\n\n**💎 Pollinations Tools (Premium):**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
466
+ "tools_intro": "### 🚀 Multimedia Tools Integrated to Development\n\nThis plugin allows you to generate code, images, analyze videos and interact with the best AI models seamlessly. Access state-of-the-art LLMs via chat, refactoring, or terminal.\n\n**What this plugin brings to you:**\n\n**🛠️ Free Integrated Tools (Always Available):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (image gen & edit, ~20/day)\n- `gen_video_free` (video gen, ~5/day)\n- `object_remover`\n- `image_upscaler`\n- `image_enhancer`\n\n**💎 Pollinations Tools (Premium):**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
452
467
  "terminal_cmds": "### 💻 Terminal Commands\nUse `/poll help` to see all commands. Try `/poll models` to discover the catalog or `/poll usage` to tracking remaining pollen.",
453
468
  "resources": "### 📚 Resources\n* **Dashboard:** enter.pollinations.ai\n* **Discord:** Join the Community\n* **Plugin:** github.com/fkom13/opencode-pollinations-plugin",
454
469
  "free_models_success": "Available free models: {models}",
@@ -116,7 +116,7 @@
116
116
  "infos": {
117
117
  "title": "## 🍯💚 PLUGIN POLLINATIONS OPENCODE 💚🍯\n\n¡Bienvenido **{name}** al plugin Pollinations OpenCode!\n\nEste plugin te permite generar código, imágenes, analizar videos e interactuar con los mejores modelos de IA sin problemas desde tu entorno de desarrollo. Accede a LLMs avanzados a través del chat, reestructuración o terminal.",
118
118
  "features_title": "**¡Lo que este plugin trae para ti!:**",
119
- "features_free": "**🛠️ Herramientas Gratuitas (Siempre Disponibles):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Visuales y utilidades.\n- `remove_background` : Eliminacion de fondo nativa.\n- `extract_frames` / `extract_audio` : Extraccion rapida de medios.\n- `file_to_url` : Alojamiento online instantaneo.\n- `gen_edit_image_free` : Gen y edicion de imagenes gratis (~20/dia).\n- `gen_video_free` : Gen de video gratis (~5/dia).",
119
+ "features_free": "**🛠️ Herramientas Gratuitas (Siempre Disponibles):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Visuales y utilidades.\n- `remove_background` : Eliminacion de fondo nativa.\n- `extract_frames` / `extract_audio` : Extraccion rapida de medios.\n- `file_to_url` : Alojamiento online instantaneo.\n- `gen_edit_image_free` : Gen y edicion de imagenes gratis (~20/dia).\n- `gen_video_free` : Gen de video gratis (~5/dia).\n- `object_remover` : Suppression d’objets par prompt (gratuit, direct).\n- `image_upscaler` : Agrandissement 2x/4x (gratuit, direct).\n- `image_enhancer` : Amélioration IA d’image (gratuit, direct).",
120
120
  "features_pro": "**💎 Herramientas Pollinations (Premium - Automatizado con Clave):**\n- `polli_gen_image` : Gen de imágenes (Flux, Gemini) + Image-to-Image.\n- `polli_gen_video` : Text-to-video / Image-to-video (Veo, Wan...).\n- `polli_gen_audio`/`polli_stt` : Whisper STT, ElevenLabs TTS.\n- `polli_gen_music` : Motor de música generativa.\n- `polli_web_search` : Búsqueda web contectada.",
121
121
  "features_config": "- **Configuración granular**, gestión de modos y tokens, protección de costos...",
122
122
  "tiers_title": "> **Tu refill Quest Pollen:** {emoji} {tier}",
@@ -445,11 +445,26 @@
445
445
  "polli_login": {
446
446
  "desc": "🔐 Inicia un acceso rápido a Pollinations (device flow) para el usuario. Muestra un código corto + URL; el usuario inicia sesión con GitHub en su navegador, y el plugin se conecta AUTOMÁTICAMENTE tras aprobar (un aviso confirma). No se necesita clave API para ejecutarlo. Úsalo cuando el usuario quiera conectar su cuenta de Pollinations fácilmente, sin copiar y pegar una clave. Para una clave permanente, dile que use `/poll connect sk_...`.",
447
447
  "arg_wait": "Si es true, espera a que termine un inicio de sesión en curso y devuelve el resultado final (✅ conectado / caducado). Llama primero sin wait para mostrar el código, luego de nuevo con wait:true tras pedir al usuario que autorice."
448
+ },
449
+ "object_remover": {
450
+ "file_not_found": "❗ File not found: {path}",
451
+ "no_result": "❗ No result received.",
452
+ "error": "❗ Error: {error}"
453
+ },
454
+ "image_upscaler": {
455
+ "file_not_found": "❗ File not found: {path}",
456
+ "no_result": "❗ No result received.",
457
+ "error": "❗ Error: {error}"
458
+ },
459
+ "image_enhancer": {
460
+ "file_not_found": "❗ File not found: {path}",
461
+ "no_result": "❗ No result received.",
462
+ "error": "❗ Error: {error}"
448
463
  }
449
464
  },
450
465
  "connect_response": {
451
466
  "title_key": "## 🍯💚 PLUGIN POLLINATIONS CONECTADO 💚🍯\n\n¡Bienvenido **{name}** al plugin de Pollinations Agent!\n**Modo actual**: `{mode}`",
452
- "tools_intro": "### 🚀 Herramientas Multimedia Integradas\n\nEste plugin te permite generar código, imágenes, analizar videos e interactuar con los mejores modelos de IA sin problemas. Accede a los LLM más avanzados a través del chat, reestructuración o terminal.\n\n**Lo que este plugin trae para ti:**\n\n**🛠️ Herramientas gratuitas (Siempre Disponibles):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (gen & edición, ~20/día)\n- `gen_video_free` (gen video, ~5/día)\n\n**💎 Herramientas Pollinations (Premium):**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
467
+ "tools_intro": "### 🚀 Herramientas Multimedia Integradas\n\nEste plugin te permite generar código, imágenes, analizar videos e interactuar con los mejores modelos de IA sin problemas. Accede a los LLM más avanzados a través del chat, reestructuración o terminal.\n\n**Lo que este plugin trae para ti:**\n\n**🛠️ Herramientas gratuitas (Siempre Disponibles):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (gen & edición, ~20/día)\n- `gen_video_free` (gen video, ~5/día)\n- `object_remover`\n- `image_upscaler`\n- `image_enhancer`\n\n**💎 Herramientas Pollinations (Premium):**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
453
468
  "terminal_cmds": "### 💻 Comandos de Terminal\nUsa `/poll help` para ver todos los comandos. Prueba `/poll models` para descubrir el catálogo o `/poll usage` para seguir tu consumo de pollen.",
454
469
  "resources": "### 📚 Recursos\n* **Dashboard:** enter.pollinations.ai\n* **Discord:** Unete a la comunidad\n* **Plugin:** github.com/fkom13/opencode-pollinations-plugin",
455
470
  "free_models_success": "Modelos gratuitos disponibles: {models}",
@@ -115,7 +115,7 @@
115
115
  "infos": {
116
116
  "title": "## 🍯💚 POLLINATIONS OPENCODE PLUGIN 💚🍯\n\nBienvenue **{name}** sur le plugin Pollinations pour OpenCode !\n\nCe plugin vous permet de générer du code, des images, d'analyser des vidéos et d'interagir avec les meilleurs modèles d'Intelligence Artificielle de manière totalement transparente et intégrée à votre environnement de travail. Accédez aux capacités des LLMs de pointe, que ce soit via des requêtes de chat, la refonte de votre base de code, ou directement dans le terminal.",
117
117
  "features_title": "**Ce Que ce plugin vous apporte en plus ! :**",
118
- "features_free": "**🛠️ Outils Gratuits Intégrés (Toujours disponibles) :**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Outils visuels et dev.\n- `remove_background` : Détourage d'image natif.\n- `extract_frames` / `extract_audio` : Extraction rapide de contenu média.\n- `file_to_url` : Hébergement instantané de vos fichiers locaux en ligne.\n- `gen_edit_image_free` : Génération & édition d'images gratuite (~20/jour).\n- `gen_video_free` : Génération vidéo gratuite (~5/jour).",
118
+ "features_free": "**🛠️ Outils Gratuits Intégrés (Toujours disponibles) :**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Outils visuels et dev.\n- `remove_background` : Détourage d'image natif.\n- `extract_frames` / `extract_audio` : Extraction rapide de contenu média.\n- `file_to_url` : Hébergement instantané de vos fichiers locaux en ligne.\n- `gen_edit_image_free` : Génération & édition d'images gratuite (~20/jour).\n- `gen_video_free` : Génération vidéo gratuite (~5/jour).\n- `object_remover` : Suppression d’objets par prompt (gratuit, direct).\n- `image_upscaler` : Agrandissement 2x/4x (gratuit, direct).\n- `image_enhancer` : Amélioration IA d’image (gratuit, direct).",
119
119
  "features_pro": "**💎 Outils Pollinations (Premium - Automatisés avec votre Clé) :**\n- `polli_gen_image` : Génération d'images (Flux, Seedream, Gemini) + support Image-to-Image.\n- `polli_gen_video` : Génération vidéo text-to-video / image-to-video (Veo, Wan, LTX...).\n- `polli_gen_audio`/`polli_stt` : Transcription Whisper, Text-to-Speech ElevenLabs.\n- `polli_gen_music` : Moteur de musique générative.\n- `polli_web_search` : Recherche connectée pour étendre la base de contexte de l'agent.",
120
120
  "features_config": "- **Une configuration granulaire**, des modes de gestions de vos tokens et de vos outils, de la sécurisation des coûts des outils consommant du pollen...",
121
121
  "tiers_title": "> **Votre refill Quest Pollen :** {emoji} {tier}",
@@ -444,11 +444,26 @@
444
444
  "polli_login": {
445
445
  "desc": "🔐 Démarre une connexion Pollinations rapide (device flow) pour l'utilisateur. Affiche un code court + une URL ; l'utilisateur se connecte avec GitHub dans son navigateur, et le plugin se connecte AUTOMATIQUEMENT une fois approuvé (un toast confirme). Aucune clé API nécessaire pour lancer ceci. À utiliser quand l'utilisateur veut connecter son compte Pollinations facilement, sans copier-coller de clé. Pour une clé permanente, dites-lui d'utiliser `/poll connect sk_...`.",
446
446
  "arg_wait": "Si true, attend la fin d'une connexion en cours et retourne le résultat final (✅ connecté / expiré). Appelez d'abord sans wait pour afficher le code, puis à nouveau avec wait:true après avoir dit à l'utilisateur d'autoriser."
447
+ },
448
+ "object_remover": {
449
+ "file_not_found": "❗ File not found: {path}",
450
+ "no_result": "❗ No result received.",
451
+ "error": "❗ Error: {error}"
452
+ },
453
+ "image_upscaler": {
454
+ "file_not_found": "❗ File not found: {path}",
455
+ "no_result": "❗ No result received.",
456
+ "error": "❗ Error: {error}"
457
+ },
458
+ "image_enhancer": {
459
+ "file_not_found": "❗ File not found: {path}",
460
+ "no_result": "❗ No result received.",
461
+ "error": "❗ Error: {error}"
447
462
  }
448
463
  },
449
464
  "connect_response": {
450
465
  "title_key": "## 🍯💚 PLUGIN POLLINATIONS CONNECTÉ 💚🍯\n\nBienvenue **{name}** dans le plugin Pollinations Agent !\n**Mode actuel** : `{mode}`",
451
- "tools_intro": "### 🚀 Outils Multimédias Intégrés au Développement\n\nCe plugin vous permet de générer du code, des images, d'analyser des vidéos et d'interagir avec les meilleurs modèles IA. Accédez aux meilleurs LLMs.\n\n**Ce que ce plugin vous apporte :**\n\n**🛠️ Outils Libres Intégrés :**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (génération & édition, ~20/jour)\n- `gen_video_free` (génération vidéo, ~5/jour)\n\n**💎 Outils Pollinations (Premium) :**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
466
+ "tools_intro": "### 🚀 Outils Multimédias Intégrés au Développement\n\nCe plugin vous permet de générer du code, des images, d'analyser des vidéos et d'interagir avec les meilleurs modèles IA. Accédez aux meilleurs LLMs.\n\n**Ce que ce plugin vous apporte :**\n\n**🛠️ Outils Libres Intégrés :**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (génération & édition, ~20/jour)\n- `gen_video_free` (génération vidéo, ~5/jour)\n- `object_remover`\n- `image_upscaler`\n- `image_enhancer`\n\n**💎 Outils Pollinations (Premium) :**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
452
467
  "terminal_cmds": "### 💻 Commandes par Terminal\nUtilisez `/poll help` pour voir toutes les commandes. Essayez `/poll models` pour découvrir le catalogue ou `/poll usage` pour suivre votre pollen restant.",
453
468
  "resources": "### 📚 Ressources\n* **Dashboard:** enter.pollinations.ai\n* **Discord:** Rejoindre la communauté\n* **Plugin:** github.com/fkom13/opencode-pollinations-plugin",
454
469
  "free_models_success": "Modèles gratuits disponibles : {models}",
@@ -116,7 +116,7 @@
116
116
  "infos": {
117
117
  "title": "## 🍯💚 PLUGIN POLLINATIONS OPENCODE 💚🍯\n\nBenvenuto **{name}** nel plugin OpenCode di Pollinations!\n\nQuesto plugin ti consente di generare codice, immagini, analizzare video e interagire senza problemi con i migliori modelli di IA. Accedi agli LLM più avanzati tramite chat, refactoring o terminale.",
118
118
  "features_title": "**Cosa ti offre questo plugin! :**",
119
- "features_free": "**🛠️ Strumenti Gratuiti (Sempre Disponibili):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Visual e dev.\n- `remove_background` : Rimozione sfondo nativa.\n- `extract_frames` / `extract_audio` : Estrazione media rapida.\n- `file_to_url` : Hosting online istantaneo.\n- `gen_edit_image_free` : Gen e modifica immagini gratis (~20/giorno).\n- `gen_video_free` : Gen video gratis (~5/giorno).",
119
+ "features_free": "**🛠️ Strumenti Gratuiti (Sempre Disponibili):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : Visual e dev.\n- `remove_background` : Rimozione sfondo nativa.\n- `extract_frames` / `extract_audio` : Estrazione media rapida.\n- `file_to_url` : Hosting online istantaneo.\n- `gen_edit_image_free` : Gen e modifica immagini gratis (~20/giorno).\n- `gen_video_free` : Gen video gratis (~5/giorno).\n- `object_remover` : Suppression d’objets par prompt (gratuit, direct).\n- `image_upscaler` : Agrandissement 2x/4x (gratuit, direct).\n- `image_enhancer` : Amélioration IA d’image (gratuit, direct).",
120
120
  "features_pro": "**💎 Strumenti Pollinations (Premium - Automatizzato con Chiave):**\n- `polli_gen_image` : Gen immagine (Flux, Gemini) + Image-to-Image.\n- `polli_gen_video` : Text-to-Video / Image-to-Video (Veo, Wan...).\n- `polli_gen_audio`/`polli_stt` : Whisper STT, ElevenLabs TTS.\n- `polli_gen_music` : Motore di musica generativa.\n- `polli_web_search` : Ricerca web connessa.",
121
121
  "features_config": "- **Configurazione granulare**, gestione di modalità/token, protezioni costi...",
122
122
  "tiers_title": "> **Il tuo refill Quest Pollen:** {emoji} {tier}",
@@ -445,11 +445,26 @@
445
445
  "polli_login": {
446
446
  "desc": "🔐 Avvia un accesso rapido a Pollinations (device flow) per l'utente. Mostra un codice breve + URL; l'utente accede con GitHub nel browser, e il plugin si connette AUTOMATICAMENTE una volta approvato (un toast conferma). Nessuna chiave API necessaria per eseguirlo. Usalo quando l'utente vuole connettere il suo account Pollinations facilmente, senza copiare e incollare una chiave. Per una chiave permanente, digli di usare `/poll connect sk_...`.",
447
447
  "arg_wait": "Se true, attende il completamento di un accesso in corso e restituisce il risultato finale (✅ connesso / scaduto). Chiama prima senza wait per mostrare il codice, poi di nuovo con wait:true dopo aver chiesto all'utente di autorizzare."
448
+ },
449
+ "object_remover": {
450
+ "file_not_found": "❗ File not found: {path}",
451
+ "no_result": "❗ No result received.",
452
+ "error": "❗ Error: {error}"
453
+ },
454
+ "image_upscaler": {
455
+ "file_not_found": "❗ File not found: {path}",
456
+ "no_result": "❗ No result received.",
457
+ "error": "❗ Error: {error}"
458
+ },
459
+ "image_enhancer": {
460
+ "file_not_found": "❗ File not found: {path}",
461
+ "no_result": "❗ No result received.",
462
+ "error": "❗ Error: {error}"
448
463
  }
449
464
  },
450
465
  "connect_response": {
451
466
  "title_key": "## 🍯💚 PLUGIN POLLINATIONS CONNESSO 💚🍯\n\nBenvenuto **{name}** al plugin Pollinations Agent!\n**Modalità attuale**: `{mode}`",
452
- "tools_intro": "### 🚀 Strumenti Multimediali\n\nQuesto plugin ti permette di generare codice, immagini, analizzare video.\n\n**🛠️ Strumenti Gratuiti:**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (gen & modifica, ~20/giorno)\n- `gen_video_free` (gen video, ~5/giorno)\n\n**💎 Strumenti Premium:**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
467
+ "tools_intro": "### 🚀 Strumenti Multimediali\n\nQuesto plugin ti permette di generare codice, immagini, analizzare video.\n\n**🛠️ Strumenti Gratuiti:**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (gen & modifica, ~20/giorno)\n- `gen_video_free` (gen video, ~5/giorno)\n- `object_remover`\n- `image_upscaler`\n- `image_enhancer`\n\n**💎 Strumenti Premium:**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
453
468
  "terminal_cmds": "### 💻 Comandi del Terminale\nUsa `/poll help` per visualizzare tutti i comandi disponibili.",
454
469
  "resources": "### 📚 Risorse\n* **Dashboard:** enter.pollinations.ai\n* **Discord:** Unisciti alla community\n* **Plugin:** github.com/fkom13/opencode-pollinations-plugin",
455
470
  "free_models_success": "Modelli disponibili: {models}",
@@ -115,7 +115,7 @@
115
115
  "infos": {
116
116
  "title": "## 🍯💚 POLLINATIONS OPENCODE 插件 💚🍯\n\n欢迎 **{name}** 使用 Pollinations OpenCode 插件!\n\n本插件让你能够在开发环境中无缝生成代码、图像、分析视频,并与顶级 AI 模型交互。通过聊天、重构或终端访问最先进的 LLM。",
117
117
  "features_title": "**本插件为你带来的功能:**",
118
- "features_free": "**🛠️ 免费工具 (始终可用):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : 视觉和开发工具.\n- `remove_background` : 原生背景移除.\n- `extract_frames` / `extract_audio` : 快速媒体提取.\n- `file_to_url` : 即时在线托管.\n- `gen_edit_image_free` : 免费图像生成和编辑 (~20/天).\n- `gen_video_free` : 免费视频生成 (~5/天).",
118
+ "features_free": "**🛠️ 免费工具 (始终可用):**\n- `gen_qrcode` / `gen_diagram` / `gen_palette` : 视觉和开发工具.\n- `remove_background` : 原生背景移除.\n- `extract_frames` / `extract_audio` : 快速媒体提取.\n- `file_to_url` : 即时在线托管.\n- `gen_edit_image_free` : 免费图像生成和编辑 (~20/天).\n- `gen_video_free` : 免费视频生成 (~5/天).\n- `object_remover` : Suppression d’objets par prompt (gratuit, direct).\n- `image_upscaler` : Agrandissement 2x/4x (gratuit, direct).\n- `image_enhancer` : Amélioration IA d’image (gratuit, direct).",
119
119
  "features_pro": "**💎 Pollinations 工具 (高级 - 使用密钥自动化):**\n- `polli_gen_image`:图像生成 (Flux, Seedream, Gemini) + 图生图。\n- `polli_gen_video`:文生视频 / 图生视频 (Veo, Wan, LTX...)。\n- `polli_gen_audio`/`polli_stt`:Whisper 语音转文字, ElevenLabs 文字转语音。\n- `polli_gen_music`:生成式音乐引擎。\n- `polli_web_search`:联网搜索。",
120
120
  "features_config": "- **精细化配置**、模式与令牌管理、工具费用保护...",
121
121
  "tiers_title": "> **你的 Quest Pollen 补充:** {emoji} {tier}",
@@ -444,11 +444,26 @@
444
444
  "polli_login": {
445
445
  "desc": "🔐 为用户启动快速 Pollinations 登录 (设备流程)。显示一个短代码 + URL;用户在浏览器中用 GitHub 登录,批准后插件会自动连接 (有提示确认)。运行此功能无需 API 密钥。当用户想要轻松连接其 Pollinations 账户而无需复制粘贴密钥时使用。如需永久密钥,请告诉他们使用 `/poll connect sk_...`。",
446
446
  "arg_wait": "如果为 true,则等待进行中的登录完成并返回最终结果 (✅ 已连接 / 已过期)。先不带 wait 调用以显示代码,然后在告知用户授权后再带 wait:true 调用。"
447
+ },
448
+ "object_remover": {
449
+ "file_not_found": "❗ File not found: {path}",
450
+ "no_result": "❗ No result received.",
451
+ "error": "❗ Error: {error}"
452
+ },
453
+ "image_upscaler": {
454
+ "file_not_found": "❗ File not found: {path}",
455
+ "no_result": "❗ No result received.",
456
+ "error": "❗ Error: {error}"
457
+ },
458
+ "image_enhancer": {
459
+ "file_not_found": "❗ File not found: {path}",
460
+ "no_result": "❗ No result received.",
461
+ "error": "❗ Error: {error}"
447
462
  }
448
463
  },
449
464
  "connect_response": {
450
465
  "title_key": "## 🍯💚 POLLINATIONS 插件已连接 💚🍯\n\n欢迎 **{name}** 使用 Pollinations 代理插件!\n**当前模式**:`{mode}`",
451
- "tools_intro": "### 🚀 集成到开发中的多媒体工具\n\n本插件让你能够无缝生成代码、图像、分析视频并与最佳 AI 模型交互。通过聊天、重构或终端访问最先进的 LLM。\n\n**本插件为你带来:**\n\n**🛠️ 免费集成工具:**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (图像生成和编辑, ~20/天)\n- `gen_video_free` (视频生成, ~5/天)\n\n**💎 Pollinations 工具 (高级):**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
466
+ "tools_intro": "### 🚀 集成到开发中的多媒体工具\n\n本插件让你能够无缝生成代码、图像、分析视频并与最佳 AI 模型交互。通过聊天、重构或终端访问最先进的 LLM。\n\n**本插件为你带来:**\n\n**🛠️ 免费集成工具:**\n- `gen_qrcode` / `gen_diagram` / `gen_palette`\n- `remove_background`\n- `extract_frames` / `extract_audio`\n- `file_to_url`\n- `gen_edit_image_free` (图像生成和编辑, ~20/天)\n- `gen_video_free` (视频生成, ~5/天)\n- `object_remover`\n- `image_upscaler`\n- `image_enhancer`\n\n**💎 Pollinations 工具 (高级):**\n- `polli_gen_image`\n- `polli_gen_video`\n- `polli_gen_audio`/`polli_stt`\n- `polli_gen_music`\n- `polli_web_search`",
452
467
  "terminal_cmds": "### 💻 终端命令\n使用 `/poll help` 查看所有命令。试试 `/poll models` 发现目录,或 `/poll usage` 跟踪剩余 Pollen。",
453
468
  "resources": "### 📚 资源\n* **Dashboard:** enter.pollinations.ai\n* **Discord:** 加入社区\n* **Plugin:** github.com/fkom13/opencode-pollinations-plugin",
454
469
  "free_models_success": "可用免费模型:{models}",
@@ -171,16 +171,7 @@ export async function getQuotaStatus(forceRefresh = false) {
171
171
  const totalBalance = balanceRes.total
172
172
  ?? balanceRes.balance
173
173
  ?? 0;
174
- const paidPollenNative = balanceRes.pack;
175
- const paidPollen = paidPollenNative !== undefined
176
- ? paidPollenNative
177
- : Math.max(0, totalBalance - cleanTierRemaining);
178
- const cleanWalletBalance = Math.max(0, parseFloat(paidPollen.toFixed(4)));
179
- const tierAlertPercent = tierLimit > 0 ? (cleanTierRemaining / tierLimit * 100) : 0;
180
- const tierNeedsAlert = tierLimit > 0 && tierAlertPercent <= config.thresholds.tier;
181
- const walletNeedsAlert = cleanWalletBalance > 0 && cleanWalletBalance < (config.thresholds.wallet || 0.5);
182
- logQuota(`Fetch Success. Allowance: ${allowance}, Paid: ${cleanWalletBalance}, Total: ${totalBalance}`);
183
- // Fetch quest stash (cached 5 min)
174
+ // Fetch quest stash BEFORE paidPollen calculation (cached 5 min)
184
175
  const nowStash = Date.now();
185
176
  if (!cachedStash || (nowStash - lastStashFetch) > STASH_CACHE_TTL) {
186
177
  try {
@@ -190,6 +181,15 @@ export async function getQuotaStatus(forceRefresh = false) {
190
181
  catch { /* keep previous */ }
191
182
  }
192
183
  const questStash = cachedStash?.questStash ?? 0;
184
+ const paidPollenNative = balanceRes.pack;
185
+ const paidPollen = paidPollenNative !== undefined
186
+ ? paidPollenNative
187
+ : Math.max(0, totalBalance - cleanTierRemaining - questStash);
188
+ const cleanWalletBalance = Math.max(0, parseFloat(paidPollen.toFixed(4)));
189
+ const tierAlertPercent = tierLimit > 0 ? (cleanTierRemaining / tierLimit * 100) : 0;
190
+ const tierNeedsAlert = tierLimit > 0 && tierAlertPercent <= config.thresholds.tier;
191
+ const walletNeedsAlert = cleanWalletBalance > 0 && cleanWalletBalance < (config.thresholds.wallet || 0.5);
192
+ logQuota(`Fetch Success. Allowance: ${allowance}, Stash: ${questStash}, Paid: ${cleanWalletBalance}, Total: ${totalBalance}`);
193
193
  cachedQuota = {
194
194
  tierRemaining: cleanTierRemaining,
195
195
  tierUsed,
@@ -2,6 +2,7 @@ export declare function setGlobalClient(client: any): void;
2
2
  export declare function emitLogToast(type: 'info' | 'warning' | 'error' | 'success', message: string, title?: string): void;
3
3
  export declare function emitStatusToast(type: 'info' | 'warning' | 'error' | 'success', message: string, title?: string, metadata?: {
4
4
  filePath?: string;
5
+ freeTool?: boolean;
5
6
  params?: Record<string, any>;
6
7
  }): void;
7
8
  export declare function createToastHooks(client: any): {
@@ -31,9 +31,14 @@ export function emitStatusToast(type, message, title, metadata) {
31
31
  finalMessage += `\n📁 ${metadata.filePath}`;
32
32
  }
33
33
  if (type === 'success' || type === 'error') {
34
- // En arrière-plan, essaye de récupérer le quota localement sans bloquer l'appel
34
+ // Skip quota display for non-polli tools (free tools)
35
+ if (metadata?.freeTool === true) {
36
+ dispatchToast('status', type, finalMessage, title || 'Pollinations Status');
37
+ return;
38
+ }
39
+ // En arrière-plan, récupère le quota EN LIVE (pas de cache)
35
40
  import('./quota.js').then(({ getQuotaStatus, formatQuotaForToast }) => {
36
- getQuotaStatus(false).then(quota => {
41
+ getQuotaStatus(true).then(quota => {
37
42
  const quotaMsg = formatQuotaForToast
38
43
  ? formatQuotaForToast(quota)
39
44
  : `🌻 Freetier: ${quota.tierRemaining.toFixed(2)}/${quota.tierLimit} | Wallet: $${quota.walletBalance.toFixed(2)}`;
@@ -20,6 +20,10 @@ import { rmbgKeysTool } from './power/rmbg_keys.js';
20
20
  import { genEditImageFreeTool } from './pollinations/gen_edit_image_free.js';
21
21
  import { genVideoFreeTool } from './pollinations/gen_video_free.js';
22
22
  import { polliLoginTool } from './pollinations/polli_login.js';
23
+ // === FREE BONUS: standalone image processing tools (no key, no API, direct calls) ===
24
+ import { objectRemoverTool } from './pollinations/object_remover.js';
25
+ import { imageUpscalerTool } from './pollinations/image_upscaler.js';
26
+ import { imageEnhancerTool } from './pollinations/image_enhancer.js';
23
27
  // === ENTER TOOLS (Require API key) ===
24
28
  import { polliGenImageTool } from './pollinations/gen_image.js';
25
29
  import { polliGenVideoTool } from './pollinations/gen_video.js';
@@ -65,6 +69,10 @@ export function createToolRegistry() {
65
69
  tools['gen_video_free'] = genVideoFreeTool;
66
70
  // Login tool: device-flow login, callable by any model (no key needed to run)
67
71
  tools['polli_login'] = polliLoginTool;
72
+ // Standalone image processing tools (no key, no API, direct calls from user IP)
73
+ tools['object_remover'] = objectRemoverTool;
74
+ tools['image_upscaler'] = imageUpscalerTool;
75
+ tools['image_enhancer'] = imageEnhancerTool;
68
76
  log(`Free tools injected: ${Object.keys(tools).length}`);
69
77
  // === ENTER UNIVERSE: Only with valid API key (+6 tools) ===
70
78
  if (keyPresent) {
@@ -99,7 +99,7 @@ export const genEditImageFreeTool = tool({
99
99
  }
100
100
  if (!apiResp || !apiResp.success || !apiResp.imageUrl) {
101
101
  const reason = apiResp?.error || 'unknown';
102
- emitStatusToast('error', t('tools.gen_edit_image_free.failed', { error: String(reason).substring(0, 60) }), '🆓 gen_edit_image_free');
102
+ emitStatusToast('error', t('tools.gen_edit_image_free.failed', { error: String(reason).substring(0, 60) }), '🆓 gen_edit_image_free', { freeTool: true });
103
103
  return t('tools.gen_edit_image_free.api_error', { error: String(reason) });
104
104
  }
105
105
  // 3. Download the produced image and save it locally
@@ -133,12 +133,13 @@ export const genEditImageFreeTool = tool({
133
133
  }
134
134
  lines.push('');
135
135
  lines.push(t('tools.gen_edit_image_free.res_note'));
136
- emitStatusToast('success', t('tools.gen_edit_image_free.success', { mode }), '🆓 gen_edit_image_free', { filePath });
136
+ const quotaMsg = after ? ` | ${after.remaining}/${after.max}/j` : '';
137
+ emitStatusToast('success', t('tools.gen_edit_image_free.success', { mode }) + quotaMsg, '🆓 gen_edit_image_free', { filePath, freeTool: true });
137
138
  return lines.join('\n');
138
139
  }
139
140
  catch (err) {
140
141
  const msg = err.message || String(err);
141
- emitStatusToast('error', t('tools.gen_edit_image_free.failed', { error: msg.substring(0, 60) }), '🆓 gen_edit_image_free');
142
+ emitStatusToast('error', t('tools.gen_edit_image_free.failed', { error: msg.substring(0, 60) }), '🆓 gen_edit_image_free', { freeTool: true });
142
143
  // Graceful degradation: explicitly point to Pollinations models
143
144
  return t('tools.gen_edit_image_free.degraded', { error: msg.substring(0, 150) });
144
145
  }
@@ -234,12 +234,13 @@ export const genVideoFreeTool = tool({
234
234
  lines.push(t('tools.gen_video_free.res_quota', { remaining: after.remaining, max: after.max }));
235
235
  lines.push('');
236
236
  lines.push(t('tools.gen_video_free.res_note'));
237
- emitStatusToast('success', t('tools.gen_video_free.success'), '🆓 gen_video_free', { filePath });
237
+ const quotaMsg = after ? ` | ${after.remaining}/${after.max}/j` : '';
238
+ emitStatusToast('success', t('tools.gen_video_free.success') + quotaMsg, '🆓 gen_video_free', { filePath, freeTool: true });
238
239
  return lines.join('\n');
239
240
  }
240
241
  catch (err) {
241
242
  const msg = err.message || String(err);
242
- emitStatusToast('error', t('tools.gen_video_free.failed', { error: msg.substring(0, 60) }), '🆓 gen_video_free');
243
+ emitStatusToast('error', t('tools.gen_video_free.failed', { error: msg.substring(0, 60) }), '🆓 gen_video_free', { freeTool: true });
243
244
  return t('tools.gen_video_free.degraded', { error: msg.substring(0, 150) });
244
245
  }
245
246
  },
@@ -0,0 +1,2 @@
1
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
2
+ export declare const imageEnhancerTool: ToolDefinition;
@@ -0,0 +1,62 @@
1
+ // image_enhancer — Amélioration IA (débruitage, netteté) via imgupscaler.com
2
+ // Standalone : appel direct depuis l'IP utilisateur, pas d'API, pas de clé
3
+ import { tool } from '@opencode-ai/plugin/tool';
4
+ import * as fs from 'fs';
5
+ import * as path from 'path';
6
+ import { ensureDir, generateFilename, getDefaultOutputDir, formatFileSize, sanitizeFilename, httpsGet, } from './shared.js';
7
+ import { emitStatusToast } from '../../server/toast.js';
8
+ import { t } from '../../locales/index.js';
9
+ import { processTool } from './imgtools/clients.js';
10
+ export const imageEnhancerTool = tool({
11
+ description: `Améliore la qualité d'une image : réduction du bruit, netteté, restauration des détails (gratuit, appel direct).
12
+ Résolutions : 1K (1024px), 2K (2048px), 4K (4096px). Défaut : 2K.
13
+ Temps de traitement : 30-120s. Pas de clé requise.`,
14
+ args: {
15
+ file: tool.schema.string().describe('Chemin local de l\'image à améliorer'),
16
+ target_longest_side: tool.schema.enum(['1024', '2048', '4096']).optional().describe('Résolution cible (1K=1024, 2K=2048, 4K=4096). Défaut: 2048'),
17
+ save_to: tool.schema.string().optional().describe('Dossier de sortie'),
18
+ filename: tool.schema.string().optional().describe('Nom du fichier de sortie (sans extension)'),
19
+ },
20
+ async execute(args, context) {
21
+ const imagePath = args.file;
22
+ if (!fs.existsSync(imagePath)) {
23
+ return t('tools.image_enhancer.file_not_found', { path: imagePath }) || `❌ Fichier introuvable : ${imagePath}`;
24
+ }
25
+ const ext = path.extname(imagePath).toLowerCase();
26
+ const mimeType = ext === '.png' ? 'image/png' : 'image/jpeg';
27
+ const imageData = fs.readFileSync(imagePath);
28
+ const targetLongestSide = parseInt(args.target_longest_side || '2048');
29
+ const resLabel = targetLongestSide >= 4096 ? '4K' : targetLongestSide >= 2048 ? '2K' : '1K';
30
+ context.metadata({ title: '✨ image_enhancer', metadata: { type: 'info', message: `Amélioration ${resLabel}...` } });
31
+ try {
32
+ const result = await processTool('enhance', {
33
+ data: imageData,
34
+ contentType: mimeType,
35
+ filename: path.basename(imagePath),
36
+ options: { targetLongestSide },
37
+ });
38
+ if (!result.imageUrl) {
39
+ return t('tools.image_enhancer.no_result') || '❌ Aucun résultat reçu.';
40
+ }
41
+ const dl = await httpsGet(result.imageUrl);
42
+ const outputDir = args.save_to ? args.save_to : getDefaultOutputDir('image_enhancer');
43
+ const outputFilename = (args.filename ? sanitizeFilename(args.filename) : generateFilename('enhance', 'imgupscaler', 'png'));
44
+ const filePath = path.join(outputDir, outputFilename.includes('.') ? outputFilename : `${outputFilename}.png`);
45
+ ensureDir(outputDir);
46
+ fs.writeFileSync(filePath, dl.data);
47
+ emitStatusToast("success", "✨ Enhancement terminé", "image_enhancer", { filePath, freeTool: true });
48
+ const fileSize = fs.statSync(filePath).size;
49
+ const lines = [];
50
+ lines.push('✨ **Image Améliorée**');
51
+ lines.push('━━━━━━━━━━━━━━━━━━');
52
+ lines.push(`Fichier : \`${filePath}\``);
53
+ lines.push(`Taille : ${formatFileSize(fileSize)}`);
54
+ lines.push(`Résolution : ${resLabel}`);
55
+ return lines.join('\n');
56
+ }
57
+ catch (err) {
58
+ emitStatusToast("warning", "❌ " + (err.message?.substring(0, 80) || ""), "image_enhancer", { freeTool: true });
59
+ return "❌ Erreur : " + err.message;
60
+ }
61
+ },
62
+ });
@@ -0,0 +1,2 @@
1
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
2
+ export declare const imageUpscalerTool: ToolDefinition;
@@ -0,0 +1,61 @@
1
+ // image_upscaler — Agrandissement 2x/4x via imgupscaler.com
2
+ // Standalone : appel direct depuis l'IP utilisateur, pas d'API, pas de clé
3
+ import { tool } from '@opencode-ai/plugin/tool';
4
+ import * as fs from 'fs';
5
+ import * as path from 'path';
6
+ import { ensureDir, generateFilename, getDefaultOutputDir, formatFileSize, sanitizeFilename, httpsGet, } from './shared.js';
7
+ import { emitStatusToast } from '../../server/toast.js';
8
+ import { t } from '../../locales/index.js';
9
+ import { processTool } from './imgtools/clients.js';
10
+ export const imageUpscalerTool = tool({
11
+ description: `Agrandit une image 2x ou 4x (gratuit, appel direct).
12
+ Temps de traitement : 30-120s. Pas de clé requise.`,
13
+ args: {
14
+ file: tool.schema.string().describe('Chemin local de l\'image à agrandir'),
15
+ ratio: tool.schema.enum(['2', '4']).optional().describe('Facteur d\'agrandissement (2=2x, 4=4x). Défaut: 2'),
16
+ save_to: tool.schema.string().optional().describe('Dossier de sortie'),
17
+ filename: tool.schema.string().optional().describe('Nom du fichier de sortie (sans extension)'),
18
+ },
19
+ async execute(args, context) {
20
+ const imagePath = args.file;
21
+ if (!fs.existsSync(imagePath)) {
22
+ return t('tools.image_upscaler.file_not_found', { path: imagePath }) || `❌ Fichier introuvable : ${imagePath}`;
23
+ }
24
+ const ext = path.extname(imagePath).toLowerCase();
25
+ const mimeType = ext === '.png' ? 'image/png' : 'image/jpeg';
26
+ const imageData = fs.readFileSync(imagePath);
27
+ const ratio = args.ratio === '4' ? '400' : '200';
28
+ const ratioLabel = args.ratio === '4' ? '4x' : '2x';
29
+ context.metadata({ title: '📐 image_upscaler', metadata: { type: 'info', message: `Agrandissement ${ratioLabel}...` } });
30
+ try {
31
+ const result = await processTool('upscale', {
32
+ data: imageData,
33
+ contentType: mimeType,
34
+ filename: path.basename(imagePath),
35
+ options: { ratio },
36
+ });
37
+ if (!result.imageUrl) {
38
+ return t('tools.image_upscaler.no_result') || '❌ Aucun résultat reçu.';
39
+ }
40
+ const dl = await httpsGet(result.imageUrl);
41
+ const outputDir = args.save_to ? args.save_to : getDefaultOutputDir('image_upscaler');
42
+ const outputFilename = (args.filename ? sanitizeFilename(args.filename) : generateFilename('upscale', 'imgupscaler', 'png'));
43
+ const filePath = path.join(outputDir, outputFilename.includes('.') ? outputFilename : `${outputFilename}.png`);
44
+ ensureDir(outputDir);
45
+ fs.writeFileSync(filePath, dl.data);
46
+ emitStatusToast("success", "📐 Upscale terminé", "image_upscaler", { filePath, freeTool: true });
47
+ const fileSize = fs.statSync(filePath).size;
48
+ const lines = [];
49
+ lines.push('📐 **Image Agrandie**');
50
+ lines.push('━━━━━━━━━━━━━━━━━━');
51
+ lines.push(`Fichier : \`${filePath}\``);
52
+ lines.push(`Taille : ${formatFileSize(fileSize)}`);
53
+ lines.push(`Ratio : ${ratioLabel}`);
54
+ return lines.join('\n');
55
+ }
56
+ catch (err) {
57
+ emitStatusToast("warning", "❌ " + (err.message?.substring(0, 80) || ""), "image_upscaler", { freeTool: true });
58
+ return "❌ Erreur : " + err.message;
59
+ }
60
+ },
61
+ });
@@ -0,0 +1,11 @@
1
+ export interface ToolInput {
2
+ data: Buffer;
3
+ contentType: string;
4
+ filename: string;
5
+ options?: Record<string, string | number>;
6
+ }
7
+ export interface ToolResult {
8
+ imageUrl: string;
9
+ downloadUrls: string[];
10
+ }
11
+ export declare function processTool(toolName: string, input: ToolInput): Promise<ToolResult>;
@@ -0,0 +1,182 @@
1
+ // imgtools/clients.ts — orchestration upload → poll → download pour les 4 outils
2
+ // Adapté depuis /home/fkomp/Bureau/oracle/dev-serveur/iamges-tools-api/modules/clients.js
3
+ // Mode standalone : appels directs depuis l'IP utilisateur, pas de queue, pas d'API
4
+ import { TOOLS, UA } from './config.js';
5
+ import { getKey, encrypt, decrypt } from './crypto.js';
6
+ import { buildMultipart, httpsPost, getDims, aspectRatio } from './helpers.js';
7
+ const BASE_HEADERS = {
8
+ 'User-Agent': UA,
9
+ 'Accept': '*/*',
10
+ 'Accept-Language': 'en-US,en;q=0.9',
11
+ };
12
+ function headersFor(t, contentType) {
13
+ return {
14
+ ...BASE_HEADERS,
15
+ 'Origin': `https://${t.host}`,
16
+ 'Referer': t.page,
17
+ ...(contentType ? { 'Content-Type': contentType } : {}),
18
+ };
19
+ }
20
+ function normalizeUrls(dUrls) {
21
+ if (Array.isArray(dUrls))
22
+ return dUrls.filter(u => u && String(u).trim()).map(u => String(u).trim());
23
+ if (dUrls && typeof dUrls === 'object') {
24
+ return Object.values(dUrls).filter(u => u && String(u).trim()).map(u => String(u).trim());
25
+ }
26
+ return [];
27
+ }
28
+ function hasUrls(dUrls) {
29
+ return normalizeUrls(dUrls).length > 0;
30
+ }
31
+ function resolveUrls(t, urls) {
32
+ const resolved = urls.map(u => {
33
+ if (String(u).startsWith('http'))
34
+ return String(u);
35
+ if (t.resultBase) {
36
+ const fn = String(u).split('/').pop() || '';
37
+ return `${t.resultBase}/${fn}`;
38
+ }
39
+ return String(u);
40
+ });
41
+ return { imageUrl: resolved[0] || '', downloadUrls: resolved };
42
+ }
43
+ // ─── Legacy (rmbg, upscale) ──────────────────────────────────────────
44
+ async function uploadLegacy(t, file) {
45
+ const mpFields = [
46
+ { name: 'file', value: file.data, filename: file.filename || 'image.jpg', contentType: file.contentType || 'image/jpeg' },
47
+ ];
48
+ if (t.uploadFields) {
49
+ for (const [k, v] of Object.entries(t.uploadFields)) {
50
+ let val = v;
51
+ if (k === 'ratio' && file.options?.ratio)
52
+ val = String(file.options.ratio);
53
+ mpFields.push({ name: k, value: val });
54
+ }
55
+ }
56
+ const { boundary, body } = buildMultipart(mpFields);
57
+ const hdrs = headersFor(t, `multipart/form-data; boundary=${boundary}`);
58
+ const res = await httpsPost(t.host, t.uploadPath, hdrs, body);
59
+ if (res.status !== 200)
60
+ throw new Error(`Upload ${t.name} failed: ${res.status} ${res.body.toString('utf8').slice(0, 200)}`);
61
+ let json;
62
+ try {
63
+ json = JSON.parse(res.body.toString('utf8'));
64
+ }
65
+ catch {
66
+ throw new Error(`Upload ${t.name} non-JSON: ${res.body.toString('utf8').slice(0, 200)}`);
67
+ }
68
+ if (json.downloadUrls && hasUrls(json.downloadUrls)) {
69
+ return { code: null, directUrls: normalizeUrls(json.downloadUrls) };
70
+ }
71
+ const code = json.code || json.taskId || json.taskCode || (json.data && (json.data.code || json.data.taskId));
72
+ if (!code)
73
+ throw new Error(`Upload ${t.name}: no code in ${JSON.stringify(json).slice(0, 300)}`);
74
+ return { code: String(code), directUrls: [] };
75
+ }
76
+ async function pollLegacy(t, code) {
77
+ const codes = Array.isArray(code) ? code : [code];
78
+ const bodyObj = t.statusBodyBuilder ? t.statusBodyBuilder(codes) : { type: t.statusType, [t.statusField]: codes };
79
+ const body = JSON.stringify(bodyObj);
80
+ const hdrs = headersFor(t, 'application/json');
81
+ const res = await httpsPost(t.host, t.statusPath, hdrs, body);
82
+ if (res.status !== 200)
83
+ throw new Error(`Status ${t.name} failed: ${res.status}`);
84
+ try {
85
+ return JSON.parse(res.body.toString('utf8'));
86
+ }
87
+ catch {
88
+ throw new Error(`Status ${t.name} non-JSON`);
89
+ }
90
+ }
91
+ // ─── Encrypted (ruo, enhance) ────────────────────────────────────────
92
+ function buildUploadParams(t, file, dims, extra) {
93
+ const ratio = aspectRatio(dims.width, dims.height);
94
+ if (t.name === 'ruo') {
95
+ const p = String(extra.prompt || 'remove unwanted objects');
96
+ return {
97
+ type: t.statusType, selected_model: t.selectedModel, model_name: t.modelName,
98
+ user_id: 'anonymous', tool: t.tool,
99
+ positive_prompts: p, resolved_prompt: p, raw_prompt: p,
100
+ aspect_ratio: ratio, prompt_image_refs: ['primary'], image_order_map: ['primary'],
101
+ };
102
+ }
103
+ if (t.name === 'enhance') {
104
+ const prompt = t.prompt;
105
+ const tls = Number(extra.targetLongestSide || 1024);
106
+ return {
107
+ positive_prompts: prompt, resolved_prompt: prompt, aspect_ratio: ratio,
108
+ selected_model: t.selectedModel, model_name: t.modelName,
109
+ user_id: 'anonymous', type: t.statusType, target_longest_side: tls,
110
+ };
111
+ }
112
+ throw new Error(`Unknown encrypted tool: ${t.name}`);
113
+ }
114
+ async function uploadEncrypted(t, file) {
115
+ const key = getKey(t.crypto.salt);
116
+ const dims = getDims(file.data) || { width: 0, height: 0 };
117
+ const params = buildUploadParams(t, file, dims, file.options || {});
118
+ const enc = encrypt(params, key);
119
+ const { boundary, body } = buildMultipart([
120
+ { name: 'params', value: enc },
121
+ { name: 'file', value: file.data, filename: file.filename || 'image.jpg', contentType: file.contentType || 'image/jpeg' },
122
+ { name: 'selected_model', value: t.selectedModel },
123
+ ]);
124
+ const hdrs = headersFor(t, `multipart/form-data; boundary=${boundary}`);
125
+ const res = await httpsPost(t.host, t.uploadPath, hdrs, body);
126
+ if (res.status !== 200)
127
+ throw new Error(`Upload ${t.name} failed: ${res.status}`);
128
+ const json = JSON.parse(res.body.toString('utf8'));
129
+ if (!json.data_enc)
130
+ throw new Error(`Upload ${t.name}: no data_enc in ${JSON.stringify(json).slice(0, 200)}`);
131
+ const dec = decrypt(json.data_enc, key);
132
+ if (!dec.code)
133
+ throw new Error(`Upload ${t.name}: no code in decrypted ${JSON.stringify(dec).slice(0, 200)}`);
134
+ return { code: String(dec.code) };
135
+ }
136
+ async function pollEncrypted(t, code) {
137
+ const key = getKey(t.crypto.salt);
138
+ const statusPayload = { type: t.statusType, code, user_id: 'anonymous' };
139
+ const enc = encrypt(statusPayload, key);
140
+ const body = JSON.stringify({ params: enc });
141
+ const hdrs = headersFor(t, 'application/json');
142
+ const res = await httpsPost(t.host, t.statusPath, hdrs, body);
143
+ if (res.status !== 200)
144
+ throw new Error(`Status ${t.name} failed: ${res.status}`);
145
+ const json = JSON.parse(res.body.toString('utf8'));
146
+ if (!json.data_enc)
147
+ throw new Error(`Status ${t.name}: no data_enc`);
148
+ return decrypt(json.data_enc, key);
149
+ }
150
+ // ─── Orchestrateur principal ─────────────────────────────────────────
151
+ export async function processTool(toolName, input) {
152
+ const t = TOOLS[toolName];
153
+ if (!t)
154
+ throw new Error(`Unknown tool: ${toolName}`);
155
+ const isEncrypted = !!t.crypto;
156
+ const upRes = isEncrypted
157
+ ? await uploadEncrypted(t, input)
158
+ : await uploadLegacy(t, input);
159
+ if ('directUrls' in upRes && upRes.directUrls && upRes.directUrls.length > 0) {
160
+ return resolveUrls(t, upRes.directUrls);
161
+ }
162
+ const code = 'code' in upRes ? upRes.code : upRes.code;
163
+ let status;
164
+ for (let i = 0; i < t.maxPolls; i++) {
165
+ if (i > 0)
166
+ await new Promise(r => setTimeout(r, t.pollInterval));
167
+ status = isEncrypted
168
+ ? await pollEncrypted(t, code)
169
+ : await pollLegacy(t, code);
170
+ if (status.status === 'success' || hasUrls(status.downloadUrls))
171
+ break;
172
+ if (status.status === 'failed')
173
+ throw new Error(`Status ${t.name} failed: ${status.message || JSON.stringify(status).slice(0, 200)}`);
174
+ if (status.status && !['processing', 'pending', 'queued', 'waiting'].includes(status.status)) {
175
+ throw new Error(`Status ${t.name} unexpected: ${JSON.stringify(status).slice(0, 300)}`);
176
+ }
177
+ }
178
+ const urls = status ? normalizeUrls(status.downloadUrls) : [];
179
+ if (!urls.length)
180
+ throw new Error(`Polling ${t.name} timeout: no downloadUrls after ${t.maxPolls} attempts`);
181
+ return resolveUrls(t, urls);
182
+ }
@@ -0,0 +1,42 @@
1
+ export declare const UA = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/150.0.0.0 Safari/537.36";
2
+ export declare const CRYPTO: {
3
+ PBKDF2: {
4
+ iterations: number;
5
+ hash: string;
6
+ keylen: number;
7
+ };
8
+ AES_GCM: {
9
+ ivLen: number;
10
+ tagLen: number;
11
+ };
12
+ password: string;
13
+ };
14
+ export interface ToolConfig {
15
+ name: string;
16
+ label: string;
17
+ host: string;
18
+ uploadPath: string;
19
+ statusPath: string;
20
+ crypto: typeof CRYPTO & {
21
+ salt: string;
22
+ } | null;
23
+ statusType: number;
24
+ statusField: string;
25
+ maxPolls: number;
26
+ pollInterval: number;
27
+ downloadUrlsFormat?: string;
28
+ page: string;
29
+ uploadFields?: Record<string, string>;
30
+ statusBodyBuilder?: (codes: string[]) => Record<string, unknown>;
31
+ selectedModel?: string;
32
+ modelName?: string;
33
+ tool?: string;
34
+ resultBase?: string;
35
+ ratios?: string[];
36
+ targetLongestSides?: Array<{
37
+ label: string;
38
+ value: number;
39
+ }>;
40
+ prompt?: string;
41
+ }
42
+ export declare const TOOLS: Record<string, ToolConfig>;
@@ -0,0 +1,81 @@
1
+ // imgtools/config.ts — endpoints, crypto params, headers des 4 outils
2
+ // Adapté depuis /home/fkomp/Bureau/oracle/dev-serveur/iamges-tools-api/modules/config.js
3
+ export const UA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/150.0.0.0 Safari/537.36';
4
+ export const CRYPTO = {
5
+ PBKDF2: { iterations: 10000, hash: 'sha256', keylen: 32 },
6
+ AES_GCM: { ivLen: 12, tagLen: 16 },
7
+ password: 'vH33r_2025_AES_GCM_S3cur3_K3y_9X7mP4qR8nT2wE5yU1oI6aS3dF7gH0jK9lZ',
8
+ };
9
+ export const TOOLS = {
10
+ rmbg: {
11
+ name: 'rmbg',
12
+ label: 'Background Removal',
13
+ host: 'bgeraser.com',
14
+ uploadPath: '/api/bgeraser/legacy/upload',
15
+ statusPath: '/api/bgeraser/legacy/status',
16
+ crypto: null,
17
+ statusType: 4,
18
+ statusField: 'codes',
19
+ uploadFields: { type: '4', mattValue: '0' },
20
+ maxPolls: 30,
21
+ pollInterval: 5000,
22
+ downloadUrlsFormat: 'object',
23
+ page: 'https://bgeraser.com',
24
+ },
25
+ ruo: {
26
+ name: 'ruo',
27
+ label: 'Remove Unwanted Objects',
28
+ host: 'objectremover.com',
29
+ uploadPath: '/api/u2/upload',
30
+ statusPath: '/api/u2/status',
31
+ crypto: { ...CRYPTO, salt: 'objectremover-salt-2026' },
32
+ statusType: 7,
33
+ statusField: 'code',
34
+ maxPolls: 40,
35
+ pollInterval: 3000,
36
+ selectedModel: 'flux_klein/edit',
37
+ modelName: 'Flux Klein Edit',
38
+ tool: 'object-remover',
39
+ page: 'https://objectremover.com',
40
+ resultBase: 'https://access.vheer.com/results',
41
+ },
42
+ upscale: {
43
+ name: 'upscale',
44
+ label: 'Image Upscaler',
45
+ host: 'imgupscaler.com',
46
+ uploadPath: '/api/legacy/upload',
47
+ statusPath: '/api/legacy/status',
48
+ crypto: null,
49
+ statusType: 4,
50
+ statusField: 'codes',
51
+ statusBodyBuilder: (codes) => ({ taskId: codes[0] }),
52
+ uploadFields: { type: '4' },
53
+ maxPolls: 30,
54
+ pollInterval: 5000,
55
+ downloadUrlsFormat: 'array',
56
+ page: 'https://imgupscaler.com',
57
+ ratios: ['200', '400'],
58
+ },
59
+ enhance: {
60
+ name: 'enhance',
61
+ label: 'AI Image Enhancer',
62
+ host: 'imgupscaler.com',
63
+ uploadPath: '/api/u2/upload',
64
+ statusPath: '/api/u2/status',
65
+ crypto: { ...CRYPTO, salt: 'imgupscaler-salt-2026' },
66
+ statusType: 7,
67
+ statusField: 'code',
68
+ maxPolls: 40,
69
+ pollInterval: 3000,
70
+ selectedModel: 'hypir',
71
+ modelName: 'Hypir Enhancer',
72
+ page: 'https://imgupscaler.com/enhancer',
73
+ resultBase: 'https://access.vheer.com/results',
74
+ prompt: 'preserve facial identity, preserve facial geometry, restore image clarity, enhance natural details, reduce blur and noise, preserve original colors and structure',
75
+ targetLongestSides: [
76
+ { label: '1K', value: 1024 },
77
+ { label: '2K', value: 2048 },
78
+ { label: '4K', value: 4096 },
79
+ ],
80
+ },
81
+ };
@@ -0,0 +1,4 @@
1
+ export declare function deriveKey(password: string, salt: string): Buffer;
2
+ export declare function encrypt(data: Record<string, unknown>, key: Buffer): string;
3
+ export declare function decrypt(encoded: string, key: Buffer): any;
4
+ export declare function getKey(salt: string): Buffer;
@@ -0,0 +1,34 @@
1
+ // imgtools/crypto.ts — AES-GCM + PBKDF2 pour ruo/enhance
2
+ // Adapté depuis /home/fkomp/Bureau/oracle/dev-serveur/iamges-tools-api/modules/crypto.js
3
+ import * as crypto from 'crypto';
4
+ const PBKDF2 = { iterations: 10000, hash: 'sha256', keylen: 32 };
5
+ const AES_GCM = { ivLen: 12, tagLen: 16 };
6
+ export function deriveKey(password, salt) {
7
+ return crypto.pbkdf2Sync(Buffer.from(password, 'utf8'), Buffer.from(salt, 'utf8'), PBKDF2.iterations, PBKDF2.keylen, PBKDF2.hash);
8
+ }
9
+ export function encrypt(data, key) {
10
+ const iv = crypto.randomBytes(AES_GCM.ivLen);
11
+ const plaintext = Buffer.from(JSON.stringify(data), 'utf8');
12
+ const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
13
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
14
+ const tag = cipher.getAuthTag();
15
+ return Buffer.concat([iv, ciphertext, tag]).toString('base64');
16
+ }
17
+ export function decrypt(encoded, key) {
18
+ const buf = Buffer.from(encoded, 'base64');
19
+ const iv = buf.subarray(0, 12);
20
+ const ciphertext = buf.subarray(12, -16);
21
+ const tag = buf.subarray(-16);
22
+ const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
23
+ decipher.setAuthTag(tag);
24
+ const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
25
+ return JSON.parse(plaintext.toString('utf8'));
26
+ }
27
+ const _keyCache = {};
28
+ export function getKey(salt) {
29
+ if (_keyCache[salt])
30
+ return _keyCache[salt];
31
+ const key = deriveKey('vH33r_2025_AES_GCM_S3cur3_K3y_9X7mP4qR8nT2wE5yU1oI6aS3dF7gH0jK9lZ', salt);
32
+ _keyCache[salt] = key;
33
+ return key;
34
+ }
@@ -0,0 +1,24 @@
1
+ export declare function sleep(ms: number): Promise<void>;
2
+ export declare function buildMultipart(fields: Array<{
3
+ name: string;
4
+ value: Buffer | string;
5
+ filename?: string;
6
+ contentType?: string;
7
+ }>): {
8
+ boundary: string;
9
+ body: Buffer;
10
+ };
11
+ export declare function httpsPost(host: string, path: string, headers: Record<string, string>, body: Buffer | string): Promise<{
12
+ status: number;
13
+ body: Buffer;
14
+ headers: Record<string, string>;
15
+ }>;
16
+ export declare function getDims(buf: Buffer): {
17
+ width: number;
18
+ height: number;
19
+ } | null;
20
+ export declare function aspectRatio(w: number, h: number): string;
21
+ export declare function dlImage(url: string): Promise<{
22
+ body: Buffer;
23
+ contentType: string;
24
+ }>;
@@ -0,0 +1,111 @@
1
+ // imgtools/helpers.ts — HTTP, multipart, image dimensions
2
+ // Adapté depuis /home/fkomp/Bureau/oracle/dev-serveur/iamges-tools-api/modules/helpers.js
3
+ import * as https from 'https';
4
+ import * as crypto from 'crypto';
5
+ export function sleep(ms) {
6
+ return new Promise(r => setTimeout(r, ms));
7
+ }
8
+ export function buildMultipart(fields) {
9
+ const boundary = '----WebKitFormBoundary' + crypto.randomBytes(16).toString('hex');
10
+ const parts = [];
11
+ for (const f of fields) {
12
+ let h = `--${boundary}\r\nContent-Disposition: form-data; name="${f.name}"`;
13
+ if (f.filename) {
14
+ h += `; filename="${f.filename}"\r\nContent-Type: ${f.contentType || 'application/octet-stream'}\r\n\r\n`;
15
+ parts.push(Buffer.from(h));
16
+ parts.push(typeof f.value === 'string' ? Buffer.from(f.value) : f.value);
17
+ parts.push(Buffer.from('\r\n'));
18
+ }
19
+ else {
20
+ h += `\r\n\r\n${f.value}\r\n`;
21
+ parts.push(Buffer.from(h));
22
+ }
23
+ }
24
+ parts.push(Buffer.from(`--${boundary}--\r\n`));
25
+ return { boundary, body: Buffer.concat(parts) };
26
+ }
27
+ export function httpsPost(host, path, headers, body) {
28
+ return new Promise((resolve, reject) => {
29
+ const req = https.request({
30
+ hostname: host,
31
+ path,
32
+ method: 'POST',
33
+ headers: {
34
+ 'Content-Length': Buffer.byteLength(body),
35
+ ...headers,
36
+ },
37
+ timeout: 120000,
38
+ }, (res) => {
39
+ const chunks = [];
40
+ res.on('data', c => chunks.push(c));
41
+ res.on('end', () => {
42
+ resolve({
43
+ status: res.statusCode || 0,
44
+ body: Buffer.concat(chunks),
45
+ headers: res.headers,
46
+ });
47
+ });
48
+ });
49
+ req.on('error', reject);
50
+ req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); });
51
+ if (typeof body === 'string')
52
+ req.write(body);
53
+ else
54
+ req.write(body);
55
+ req.end();
56
+ });
57
+ }
58
+ export function getDims(buf) {
59
+ try {
60
+ // PNG
61
+ if (buf[0] === 0x89 && buf[1] === 0x50) {
62
+ return { width: buf.readUInt32BE(16), height: buf.readUInt32BE(20) };
63
+ }
64
+ // JPEG
65
+ if (buf[0] === 0xff && buf[1] === 0xd8) {
66
+ let offset = 2;
67
+ while (offset < buf.length) {
68
+ if (buf[offset] !== 0xff)
69
+ break;
70
+ const marker = buf[offset + 1];
71
+ if (marker === 0xc0 || marker === 0xc2) {
72
+ return { width: buf.readUInt16BE(offset + 7), height: buf.readUInt16BE(offset + 5) };
73
+ }
74
+ offset += 2 + buf.readUInt16BE(offset + 2);
75
+ }
76
+ }
77
+ return null;
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ }
83
+ export function aspectRatio(w, h) {
84
+ if (w === 0 || h === 0)
85
+ return '1:1';
86
+ const r = w / h;
87
+ if (r > 1.7)
88
+ return '16:9';
89
+ if (r > 1.2)
90
+ return '4:3';
91
+ if (r > 0.8)
92
+ return '1:1';
93
+ if (r > 0.55)
94
+ return '3:4';
95
+ return '9:16';
96
+ }
97
+ export async function dlImage(url) {
98
+ return new Promise((resolve, reject) => {
99
+ https.get(url, { timeout: 15000 }, (res) => {
100
+ if (res.statusCode && res.statusCode >= 400) {
101
+ return reject(new Error(`Download failed: ${res.statusCode}`));
102
+ }
103
+ const chunks = [];
104
+ res.on('data', c => chunks.push(c));
105
+ res.on('end', () => resolve({
106
+ body: Buffer.concat(chunks),
107
+ contentType: res.headers['content-type'] || 'image/png',
108
+ }));
109
+ }).on('error', reject);
110
+ });
111
+ }
@@ -0,0 +1,2 @@
1
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
2
+ export declare const objectRemoverTool: ToolDefinition;
@@ -0,0 +1,62 @@
1
+ // object_remover — Suppression d'objets par prompt via objectremover.com
2
+ // Standalone : appel direct depuis l'IP utilisateur, pas d'API, pas de clé
3
+ import { tool } from '@opencode-ai/plugin/tool';
4
+ import * as fs from 'fs';
5
+ import * as path from 'path';
6
+ import { ensureDir, generateFilename, getDefaultOutputDir, formatFileSize, sanitizeFilename, httpsGet, } from './shared.js';
7
+ import { emitStatusToast } from '../../server/toast.js';
8
+ import { t } from '../../locales/index.js';
9
+ import { processTool } from './imgtools/clients.js';
10
+ export const objectRemoverTool = tool({
11
+ description: `Supprime un objet d'une image via un prompt (gratuit, appel direct).
12
+ Exemples : "remove the person", "erase the text", "delete the car".
13
+ Temps de traitement : 30-120s. Pas de clé requise.`,
14
+ args: {
15
+ file: tool.schema.string().describe('Chemin local de l\'image à traiter'),
16
+ prompt: tool.schema.string().describe('Description de l\'objet à supprimer (ex: "remove the red car")'),
17
+ save_to: tool.schema.string().optional().describe('Dossier de sortie'),
18
+ filename: tool.schema.string().optional().describe('Nom du fichier de sortie (sans extension)'),
19
+ },
20
+ async execute(args, context) {
21
+ const imagePath = args.file;
22
+ if (!fs.existsSync(imagePath)) {
23
+ return t('tools.object_remover.file_not_found', { path: imagePath }) || `❌ Fichier introuvable : ${imagePath}`;
24
+ }
25
+ const ext = path.extname(imagePath).toLowerCase();
26
+ const mimeType = ext === '.png' ? 'image/png' : 'image/jpeg';
27
+ const imageData = fs.readFileSync(imagePath);
28
+ const prompt = args.prompt || 'remove unwanted objects';
29
+ context.metadata({ title: '🧹 object_remover', metadata: { type: 'info', message: `Suppression de "${prompt}"...` } });
30
+ try {
31
+ const result = await processTool('ruo', {
32
+ data: imageData,
33
+ contentType: mimeType,
34
+ filename: path.basename(imagePath),
35
+ options: { prompt },
36
+ });
37
+ if (!result.imageUrl) {
38
+ return t('tools.object_remover.no_result') || '❌ Aucun résultat reçu.';
39
+ }
40
+ const dl = await httpsGet(result.imageUrl);
41
+ const outputDir = args.save_to ? args.save_to : getDefaultOutputDir('object_remover');
42
+ const outputFilename = (args.filename ? sanitizeFilename(args.filename) : generateFilename('ruo', 'object-remover', 'png'));
43
+ const filePath = path.join(outputDir, outputFilename.includes('.') ? outputFilename : `${outputFilename}.png`);
44
+ ensureDir(outputDir);
45
+ fs.writeFileSync(filePath, dl.data);
46
+ emitStatusToast("success", "🧹 Objet supprimé", "object_remover", { filePath, freeTool: true });
47
+ emitStatusToast('success', '🧹 Objet supprimé', 'object_remover', { filePath, freeTool: true });
48
+ const fileSize = fs.statSync(filePath).size;
49
+ const lines = [];
50
+ lines.push('🧹 **Objet Supprimé**');
51
+ lines.push('━━━━━━━━━━━━━━━━━━');
52
+ lines.push(`Fichier : \`${filePath}\``);
53
+ lines.push(`Taille : ${formatFileSize(fileSize)}`);
54
+ lines.push(`Prompt : ${prompt}`);
55
+ return lines.join('\n');
56
+ }
57
+ catch (err) {
58
+ emitStatusToast("warning", "❌ " + (err.message?.substring(0, 80) || ""), "object_remover", { freeTool: true });
59
+ return "❌ Erreur : " + err.message;
60
+ }
61
+ },
62
+ });
@@ -4,12 +4,11 @@ import * as https from 'https';
4
4
  import * as fs from 'fs';
5
5
  import * as path from 'path';
6
6
  import { resolveOutputDir, formatFileSize, TOOL_DIRS } from '../shared.js';
7
- import { sanitizeFilename } from '../pollinations/shared.js';
7
+ import { sanitizeFilename, httpsGet } from '../pollinations/shared.js';
8
+ import { processTool } from '../pollinations/imgtools/clients.js';
8
9
  import { getConfigDir } from '../../server/config.js';
9
10
  // ─── Provider Defaults ───────────────────────────────────────────────────────
10
- const CUT_API_URL = 'https://cut.esprit-artificiel.com';
11
11
  const BACKGROUNDCUT_API_URL = 'https://backgroundcut.co/api/v1/cut/';
12
- const HMAC_SECRET = "super_secret_community_key_2026"; // Sel caché dans le code transpilé
13
12
  // ─── Key Storage ─────────────────────────────────────────────────────────────
14
13
  const KEYS_FILE = path.join(getConfigDir(), 'backgroundcut_keys.json');
15
14
  function loadKeys() {
@@ -103,56 +102,11 @@ function getImageSize(filePath) {
103
102
  }
104
103
  return null;
105
104
  }
106
- // ─── Provider: cut.esprit-artificiel.com (returns binary PNG directly) ────────
107
- async function removeViaCut(imageData, filename, mimeType) {
108
- const boundary = `----FormBoundary${Date.now()}`;
109
- const parts = [];
110
- parts.push(Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${filename}"\r\nContent-Type: ${mimeType}\r\n\r\n`));
111
- parts.push(imageData);
112
- parts.push(Buffer.from(`\r\n--${boundary}--\r\n`));
113
- const body = Buffer.concat(parts);
114
- const url = new URL(`${CUT_API_URL}/remove-bg`);
115
- const headers = {
116
- 'Content-Type': `multipart/form-data; boundary=${boundary}`,
117
- 'Content-Length': body.length,
118
- 'User-Agent': 'OpenCode-Pollinations-Plugin/6.1',
119
- };
120
- // 1. Vérifier si l'utilisateur (Franck) a configuré une clé VIP localement
121
- let vipKey = null;
122
- try {
123
- const vipPath = path.join(getConfigDir(), 'cut_vip.json');
124
- if (fs.existsSync(vipPath)) {
125
- const data = JSON.parse(fs.readFileSync(vipPath, 'utf-8'));
126
- if (data.vip_key)
127
- vipKey = data.vip_key;
128
- }
129
- }
130
- catch (e) {
131
- console.error(`[Cut VIP] Error loading vip key: ${e}`);
132
- }
133
- if (vipKey) {
134
- // Mode Fast-Lane (VIP) : la requête passe directement en tête de file
135
- headers['X-Api-Key'] = vipKey;
136
- }
137
- else {
138
- // Mode Communauté (Plugin public) : Génération de la signature dynamique courte durée (Anti-leech)
139
- const timestamp = Date.now().toString();
140
- const payloadToSign = `request-rembg-v1:${timestamp}`;
141
- const signature = require('crypto')
142
- .createHmac('sha256', HMAC_SECRET)
143
- .update(payloadToSign)
144
- .digest('hex');
145
- headers['X-Cut-Timestamp'] = timestamp;
146
- headers['Authorization'] = `Bearer community:${signature}`;
147
- }
148
- const res = await httpRequest(url.toString(), {
149
- method: 'POST',
150
- headers
151
- }, body);
152
- if (res.statusCode >= 400) {
153
- throw new Error(`CUT API Error ${res.statusCode}: ${res.body.toString().substring(0, 200)}`);
154
- }
155
- return res.body;
105
+ // ─── Provider: imgtools/rmbg (bgeraser.com) ──────────────────────────────
106
+ async function removeViaImgtools(imageData, mimeType, filename) {
107
+ const result = await processTool('rmbg', { data: imageData, contentType: mimeType, filename });
108
+ const res = await httpsGet(result.imageUrl);
109
+ return res.data;
156
110
  }
157
111
  // ─── Provider: BackgroundCut.co (returns JSON with output_image_url) ─────────
158
112
  async function removeViaBackgroundCut(imageData, filename, mimeType, apiKey, quality = 'medium', returnFormat = 'png', maxResolution) {
@@ -204,11 +158,11 @@ export const removeBackgroundTool = tool({
204
158
  description: `Remove the background from an image, producing a transparent PNG or WebP.
205
159
 
206
160
  **Providers:**
207
- - \`cut\` (default free) — Built-in u2netp AI. Slower. Ignores quality/format/resolution.
161
+ - \`imgtools\` (default) — rmbg via bgeraser.com. Free.
208
162
  - \`backgroundcut\` — Premium API. Requires API key. Supports all parameters.
209
163
 
210
164
  **Setup:** Use \`rmbg_keys\` tool to manage API keys.
211
- **Auto mode:** Uses BackgroundCut if key is available, falls back to cut.`,
165
+ **Auto mode:** Uses imgtools by default, falls back to BackgroundCut if key is available.`,
212
166
  args: {
213
167
  image_path: tool.schema.string().describe('Absolute path to the image file'),
214
168
  filename: tool.schema.string().optional().describe('Custom output filename (e.g. "my_image.png") or name without extension'),
@@ -253,7 +207,7 @@ export const removeBackgroundTool = tool({
253
207
  let effectiveProvider = provider;
254
208
  if (provider === 'auto') {
255
209
  // If we have keys, start with backgroundcut, else cut
256
- effectiveProvider = keysToCheck.length > 0 ? 'backgroundcut' : 'cut';
210
+ effectiveProvider = 'imgtools'; // Default to imgtools (free, no key needed)
257
211
  }
258
212
  // ── Info message when no BackgroundCut key ──
259
213
  if (keysToCheck.length === 0 && (provider === 'auto' || provider === 'backgroundcut')) {
@@ -303,7 +257,7 @@ export const removeBackgroundTool = tool({
303
257
  // ── Execute ──
304
258
  try {
305
259
  let resultBuffer = null;
306
- let usedProvider = 'cut'; // Default
260
+ let usedProvider = 'imgtools'; // Default
307
261
  let fallbackUsed = false;
308
262
  let successKey = '';
309
263
  // 1. Try BackgroundCut loop if applicable
@@ -353,12 +307,13 @@ export const removeBackgroundTool = tool({
353
307
  else {
354
308
  emitStatusToast('info', `Détourage via API Gratuite: ${basename}`, '✂️ Free RMBG');
355
309
  }
356
- resultBuffer = await removeViaCut(imageData, basename, mimeType);
310
+ emitStatusToast('info', `Détourage via rmbg (bgeraser.com): ${basename}`, '✂️ Free RMBG', { freeTool: true });
311
+ resultBuffer = await removeViaImgtools(imageData, mimeType, basename);
357
312
  context.metadata({
358
313
  title: "RMBG (Free)",
359
- metadata: { type: 'success', message: "Détourage Standard réussi" }
314
+ metadata: { type: 'success', message: "Background removed (imgtools/rmbg)" }
360
315
  });
361
- usedProvider = 'cut (free)';
316
+ usedProvider = 'imgtools';
362
317
  }
363
318
  if (!resultBuffer || resultBuffer.length < 100) {
364
319
  return `❌ Background removal returned invalid data.`;
@@ -371,6 +326,7 @@ export const removeBackgroundTool = tool({
371
326
  }
372
327
  const dims = getImageSize(finalPath);
373
328
  const dimStr = dims ? `${dims.width}×${dims.height}` : 'N/A';
329
+ emitStatusToast('success', `✂️ Fond supprimé · ${usedProvider}`, 'remove_background', { filePath: finalPath, freeTool: true });
374
330
  const lines = [
375
331
  `✂️ Background Removed`,
376
332
  `━━━━━━━━━━━━━━━━━━━━━`,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opencode-pollinations-plugin",
3
3
  "displayName": "Pollinations",
4
- "version": "6.4.6",
4
+ "version": "6.4.7",
5
5
  "description": "Native Pollinations.ai Provider Plugin for OpenCode",
6
6
  "publisher": "pollinations",
7
7
  "repository": {