opencode-pollinations-plugin 6.1.0-beta.2 → 6.1.0-beta.22

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.
Files changed (96) hide show
  1. package/README.md +242 -62
  2. package/dist/index.js +68 -159
  3. package/dist/server/commands.d.ts +6 -0
  4. package/dist/server/commands.js +400 -71
  5. package/dist/server/config.d.ts +32 -23
  6. package/dist/server/config.js +183 -99
  7. package/dist/server/connect-response.d.ts +2 -0
  8. package/dist/server/connect-response.js +141 -0
  9. package/dist/server/generate-config.d.ts +3 -30
  10. package/dist/server/generate-config.js +164 -106
  11. package/dist/server/index.d.ts +2 -1
  12. package/dist/server/index.js +124 -149
  13. package/dist/server/logger.d.ts +8 -0
  14. package/dist/server/logger.js +36 -0
  15. package/dist/server/models/cache.d.ts +35 -0
  16. package/dist/server/models/cache.js +160 -0
  17. package/dist/server/models/fetcher.d.ts +18 -0
  18. package/dist/server/models/fetcher.js +150 -0
  19. package/dist/server/models/index.d.ts +6 -0
  20. package/dist/server/models/index.js +5 -0
  21. package/dist/server/models/manual.d.ts +15 -0
  22. package/dist/server/models/manual.js +92 -0
  23. package/dist/server/models/types.d.ts +55 -0
  24. package/dist/server/models/types.js +7 -0
  25. package/dist/server/models/worker.d.ts +21 -0
  26. package/dist/server/models/worker.js +97 -0
  27. package/dist/server/pollinations-api.d.ts +11 -0
  28. package/dist/server/pollinations-api.js +21 -8
  29. package/dist/server/proxy.js +223 -160
  30. package/dist/server/quota.d.ts +2 -0
  31. package/dist/server/quota.js +89 -86
  32. package/dist/server/scripts/pollinations_pricing.d.ts +8 -0
  33. package/dist/server/scripts/pollinations_pricing.js +246 -0
  34. package/dist/server/scripts/test_cost_endpoints.d.ts +1 -0
  35. package/dist/server/scripts/test_cost_endpoints.js +61 -0
  36. package/dist/server/scripts/test_dynamic_pricing.d.ts +1 -0
  37. package/dist/server/scripts/test_dynamic_pricing.js +39 -0
  38. package/dist/server/scripts/test_freetier_audit.d.ts +11 -0
  39. package/dist/server/scripts/test_freetier_audit.js +215 -0
  40. package/dist/server/scripts/test_parallel_cost.d.ts +1 -0
  41. package/dist/server/scripts/test_parallel_cost.js +104 -0
  42. package/dist/server/toast.d.ts +7 -1
  43. package/dist/server/toast.js +43 -10
  44. package/dist/tools/design/gen_diagram.d.ts +2 -0
  45. package/dist/tools/design/gen_diagram.js +94 -0
  46. package/dist/tools/design/gen_palette.d.ts +2 -0
  47. package/dist/tools/design/gen_palette.js +182 -0
  48. package/dist/tools/design/gen_qrcode.d.ts +2 -0
  49. package/dist/tools/design/gen_qrcode.js +50 -0
  50. package/dist/tools/ffmpeg.d.ts +24 -0
  51. package/dist/tools/ffmpeg.js +54 -0
  52. package/dist/tools/index.d.ts +24 -0
  53. package/dist/tools/index.js +83 -0
  54. package/dist/tools/pollinations/beta_discovery.d.ts +9 -0
  55. package/dist/tools/pollinations/beta_discovery.js +197 -0
  56. package/dist/tools/pollinations/cost-guard.d.ts +38 -0
  57. package/dist/tools/pollinations/cost-guard.js +141 -0
  58. package/dist/tools/pollinations/deepsearch.d.ts +7 -0
  59. package/dist/tools/pollinations/deepsearch.js +80 -0
  60. package/dist/tools/pollinations/gen_audio.d.ts +18 -0
  61. package/dist/tools/pollinations/gen_audio.js +246 -0
  62. package/dist/tools/pollinations/gen_image.d.ts +11 -0
  63. package/dist/tools/pollinations/gen_image.js +225 -0
  64. package/dist/tools/pollinations/gen_music.d.ts +14 -0
  65. package/dist/tools/pollinations/gen_music.js +180 -0
  66. package/dist/tools/pollinations/gen_video.d.ts +16 -0
  67. package/dist/tools/pollinations/gen_video.js +256 -0
  68. package/dist/tools/pollinations/polli_gen_confirm.d.ts +2 -0
  69. package/dist/tools/pollinations/polli_gen_confirm.js +48 -0
  70. package/dist/tools/pollinations/polli_status.d.ts +2 -0
  71. package/dist/tools/pollinations/polli_status.js +31 -0
  72. package/dist/tools/pollinations/polli_web_search.d.ts +15 -0
  73. package/dist/tools/pollinations/polli_web_search.js +164 -0
  74. package/dist/tools/pollinations/search_crawl_scrape.d.ts +7 -0
  75. package/dist/tools/pollinations/search_crawl_scrape.js +85 -0
  76. package/dist/tools/pollinations/shared.d.ts +165 -0
  77. package/dist/tools/pollinations/shared.js +665 -0
  78. package/dist/tools/pollinations/test_estimators.d.ts +1 -0
  79. package/dist/tools/pollinations/test_estimators.js +22 -0
  80. package/dist/tools/pollinations/transcribe_audio.d.ts +13 -0
  81. package/dist/tools/pollinations/transcribe_audio.js +194 -0
  82. package/dist/tools/power/extract_audio.d.ts +2 -0
  83. package/dist/tools/power/extract_audio.js +179 -0
  84. package/dist/tools/power/extract_frames.d.ts +2 -0
  85. package/dist/tools/power/extract_frames.js +237 -0
  86. package/dist/tools/power/file_to_url.d.ts +2 -0
  87. package/dist/tools/power/file_to_url.js +217 -0
  88. package/dist/tools/power/remove_background.d.ts +2 -0
  89. package/dist/tools/power/remove_background.js +366 -0
  90. package/dist/tools/power/rmbg_keys.d.ts +2 -0
  91. package/dist/tools/power/rmbg_keys.js +79 -0
  92. package/dist/tools/shared.d.ts +30 -0
  93. package/dist/tools/shared.js +80 -0
  94. package/package.json +10 -4
  95. package/dist/server/models-seed.d.ts +0 -18
  96. package/dist/server/models-seed.js +0 -55
@@ -1,9 +1,9 @@
1
1
  import * as https from 'https';
2
- import { loadConfig, saveConfig } from './config.js';
3
- import { getQuotaStatus } from './quota.js';
2
+ import { loadConfig, saveConfig, saveKeyToAuthJson } from './config.js';
3
+ import { getQuotaStatus, fetchUsageForPeriod } from './quota.js';
4
4
  import { emitStatusToast } from './toast.js';
5
- import { getDetailedUsage } from './pollinations-api.js';
6
5
  import { generatePollinationsConfig } from './generate-config.js';
6
+ import { ModelRegistry } from './models/index.js';
7
7
  function checkEndpoint(ep, key) {
8
8
  return new Promise((resolve) => {
9
9
  const req = https.request({
@@ -57,6 +57,7 @@ export async function checkKeyPermissions(key) {
57
57
  }
58
58
  // === CONSTANTS & PRICING ===
59
59
  const TIER_LIMITS = {
60
+ microbe: { pollen: 0.1, emoji: '🦠' },
60
61
  spore: { pollen: 1, emoji: '🦠' },
61
62
  seed: { pollen: 3, emoji: '🌱' },
62
63
  flower: { pollen: 10, emoji: '🌸' },
@@ -130,6 +131,10 @@ function calculateCurrentPeriodStats(usage, lastReset, tierLimit) {
130
131
  };
131
132
  }
132
133
  // === COMMAND HANDLER ===
134
+ let globalClient = null;
135
+ export function setClientForCommands(client) {
136
+ globalClient = client;
137
+ }
133
138
  export async function handleCommand(command) {
134
139
  const parts = command.trim().split(/\s+/);
135
140
  if (!parts[0].startsWith('/poll')) {
@@ -150,6 +155,19 @@ export async function handleCommand(command) {
150
155
  return handleConfigCommand(args);
151
156
  case 'help':
152
157
  return handleHelpCommand();
158
+ case 'models':
159
+ return await handleModelsCommand(args);
160
+ case 'pricing':
161
+ return await handlePricingCommand();
162
+ case 'infos':
163
+ return await handleInfosCommand();
164
+ case 'addKey': // External trigger
165
+ // UI Pollution Fix: User hates appendPrompt.
166
+ // Just return a message telling them to use the tool.
167
+ return {
168
+ handled: true,
169
+ response: "💡 Pour ajouter une clé : Utilisez l'outil `rmbg_keys`\nExemple : `rmbg_keys action=add key=bkgc_...`"
170
+ };
153
171
  default:
154
172
  return {
155
173
  handled: true,
@@ -202,7 +220,7 @@ async function handleModeCommand(args) {
202
220
  return { handled: true, error: `❌ Erreur de vérification: ${e.message}` };
203
221
  }
204
222
  }
205
- // Allow switch (if economy or manual, or verified pro)
223
+ // Allow switch (if alwaysfree or manual, or verified pro)
206
224
  saveConfig({ mode: mode });
207
225
  const config = loadConfig();
208
226
  if (config.gui.status !== 'none') {
@@ -213,7 +231,7 @@ async function handleModeCommand(args) {
213
231
  response: `✅ Mode changé: ${mode}`
214
232
  };
215
233
  }
216
- async function handleUsageCommand(args) {
234
+ export async function handleUsageCommand(args) {
217
235
  const isFull = args[0] === 'full';
218
236
  try {
219
237
  const quota = await getQuotaStatus(true);
@@ -233,10 +251,10 @@ async function handleUsageCommand(args) {
233
251
  response += `\n> ⚠️ *Votre clé API ne permet pas l'accès aux détails d'usage (Restriction).*`;
234
252
  }
235
253
  else {
236
- const usageData = await getDetailedUsage(config.apiKey);
237
- if (usageData && usageData.usage) {
238
- const lastReset = calculateResetDate(quota.nextResetAt);
239
- const stats = calculateCurrentPeriodStats(usageData.usage, lastReset, quota.tierLimit);
254
+ const lastReset = calculateResetDate(quota.nextResetAt);
255
+ const usageData = await fetchUsageForPeriod(config.apiKey, lastReset);
256
+ if (usageData && usageData.length > 0) {
257
+ const stats = calculateCurrentPeriodStats(usageData, lastReset, quota.tierLimit);
240
258
  response += `\n### 📊 Détail Période (depuis ${lastReset.toLocaleTimeString()})\n`;
241
259
  response += `**Total Requêtes**: ${stats.totalRequests} | **Tokens**: In ${formatTokens(stats.inputTokens)} / Out ${formatTokens(stats.outputTokens)}\n\n`;
242
260
  response += `| Modèle | Reqs | Coût | Tokens |\n`;
@@ -264,36 +282,32 @@ async function handleUsageCommand(args) {
264
282
  }
265
283
  }
266
284
  function handleFallbackCommand(args) {
267
- const [mode, model] = args;
268
- if (!mode) {
285
+ const [main, agent] = args;
286
+ if (!main) {
269
287
  const config = loadConfig();
288
+ const freeConfig = `Free: main=${config.fallbacks.free.main}, agent=${config.fallbacks.free.agent}`;
289
+ const enterConfig = `Enter: agent=${config.fallbacks.enter.agent}`;
270
290
  return {
271
291
  handled: true,
272
- response: `Fallbacks actuels:\n- Economy: ${config.fallbacks.economy}\n- Pro: ${config.fallbacks.pro}`
292
+ response: `Fallbacks actuels:\n${freeConfig}\n${enterConfig}`
273
293
  };
274
294
  }
295
+ // Default behavior for "/poll fallback <model> <agent>" is setting FREE fallbacks
296
+ // User needs to use commands (maybe add /poll fallback enter ...) later
297
+ // For now, map to Free Fallback as it's the primary Safety Net
275
298
  const config = loadConfig();
276
- if (mode === 'economy' && model) {
277
- saveConfig({
278
- fallbacks: { ...config.fallbacks, economy: model }
279
- });
280
- return {
281
- handled: true,
282
- response: `✅ Fallback Economy configuré: ${model}`
283
- };
284
- }
285
- if (mode === 'pro' && model) {
286
- saveConfig({
287
- fallbacks: { ...config.fallbacks, pro: model }
288
- });
289
- return {
290
- handled: true,
291
- response: `✅ Fallback Pro configuré: ${model}`
292
- };
293
- }
299
+ saveConfig({
300
+ fallbacks: {
301
+ ...config.fallbacks,
302
+ free: {
303
+ main: main,
304
+ agent: agent || config.fallbacks.free.agent
305
+ }
306
+ }
307
+ });
294
308
  return {
295
309
  handled: true,
296
- error: `Usage: /pollinations fallback [economy|pro] <modèle>`
310
+ response: `✅ Fallback (Free) configuré: main=${main}, agent=${agent || config.fallbacks.free.agent}`
297
311
  };
298
312
  }
299
313
  async function handleConnectCommand(args) {
@@ -307,15 +321,16 @@ async function handleConnectCommand(args) {
307
321
  // 1. Universal Validation (No Syntax Check) - Functional Check
308
322
  emitStatusToast('info', 'Vérification de la clé...', 'Pollinations Config');
309
323
  try {
310
- const models = await generatePollinationsConfig(key);
311
- // 2. Check if we got real models (not just connect placeholder)
312
- const realModels = models.filter(m => m.id !== 'connect');
313
- if (realModels.length > 0) {
324
+ const models = await generatePollinationsConfig(key, true);
325
+ // 2. Check if we got Enterprise models
326
+ const enterpriseModels = models.filter(m => m.id.startsWith('enter/'));
327
+ if (enterpriseModels.length > 0) {
314
328
  // SUCCESS
315
329
  saveConfig({ apiKey: key }); // Don't force mode 'pro'. Let user decide.
330
+ saveKeyToAuthJson(key); // NATIVE SYNC: Hot-reload on OpenCode bypasses restart requirement !
316
331
  const masked = key.substring(0, 6) + '...';
317
332
  // Count Paid Only models found
318
- const diamondCount = realModels.filter(m => m.name.includes('💎')).length;
333
+ const diamondCount = enterpriseModels.filter(m => m.name.includes('💎')).length;
319
334
  // CHECK RESTRICTIONS: Strict Check (Usage + Profile + Balance)
320
335
  let forcedModeMsg = "";
321
336
  let isLimited = false;
@@ -340,10 +355,10 @@ async function handleConnectCommand(args) {
340
355
  else {
341
356
  saveConfig({ apiKey: key, keyHasAccessToProfile: true }); // Let user keep current mode or default
342
357
  }
343
- emitStatusToast('success', `Clé Valide! (${realModels.length} modèles débloqués)`, 'Pollinations Config');
358
+ emitStatusToast('success', `Clé Valide! (${enterpriseModels.length} modèles Pro débloqués)`, 'Pollinations Config');
344
359
  return {
345
360
  handled: true,
346
- response: `✅ **Connexion Réussie!**\n- Clé: \`${masked}\`\n- Modèles Débloqués: ${realModels.length} (dont ${diamondCount} 💎 Paid)${forcedModeMsg}`
361
+ response: `✅ **Connexion Réussie! (Injection à chaud)**\n- Clé: \`${masked}\`\n- Modèles Pro Débloqués: ${enterpriseModels.length} (dont ${diamondCount} 💎 Paid)${forcedModeMsg}`
347
362
  };
348
363
  }
349
364
  else {
@@ -355,8 +370,18 @@ async function handleConnectCommand(args) {
355
370
  // Wait, generate-config falls back to providing a list containing "[Enter] GPT-4o (Fallback)" if fetch failed.
356
371
  // So we need to detect if it's a "REAL" fetch or a "FALLBACK" fetch.
357
372
  // The fallback models have `variants: {}` usually, but real ones might too.
358
- // v6.0: No fallback prefix check needed. If models is empty (only connect), key is invalid.
359
- throw new Error("Aucun modèle détecté pour cette clé. Clé invalide ou expirée.");
373
+ // A better check: The fallback list is hardcoded in generate-config.ts catch block.
374
+ // Let's modify generate-config to return EMPTY list on error?
375
+ // Or just check if the returned models work?
376
+ // Simplest: If `generatePollinationsConfig` returns any model starting with `enter/` that includes "(Fallback)" in name, we assume failure?
377
+ // "GPT-4o (Fallback)" is the name.
378
+ const isFallback = models.some(m => m.name.includes('(Fallback)') && m.id.startsWith('enter/'));
379
+ if (isFallback) {
380
+ throw new Error("Clé rejetée par l'API (Accès refusé ou invalide).");
381
+ }
382
+ // If we are here, we got no enter models, or empty list?
383
+ // If key is valid but has no access?
384
+ throw new Error("Aucun modèle Enterprise détecté pour cette clé.");
360
385
  }
361
386
  }
362
387
  catch (e) {
@@ -373,9 +398,29 @@ function handleConfigCommand(args) {
373
398
  const [key, value] = args;
374
399
  if (!key) {
375
400
  const config = loadConfig();
401
+ const k = config.apiKey ? (config.apiKey.length > 8 ? `${config.apiKey.substring(0, 5)}****${config.apiKey.substring(config.apiKey.length - 4)}` : '****') : 'Non configurée';
402
+ const markdownResponse = `## ⚙️ Configuration Pollinations
403
+ Voici l'état actuel de votre configuration locale.
404
+
405
+ | Paramètre | Valeur Actuelle | Rôle | Commande |
406
+ |-----------|-----------------|------|----------|
407
+ | **apiKey** | \`${k}\` | Votre clé API secrète (BYOK) | \`/pollinations connect <key>\` |
408
+ | **mode** | \`${config.mode}\` | Mode d'accès | \`/pollinations mode <manual/pro/alwaysfree>\` |
409
+ | **enablePaidTools**| \`${config.enablePaidTools ?? true}\` | Sécurité: Désactiver outils payants | \`/poll config enablePaidTools <true/false>\` |
410
+ | **costConfirmationRequired**| \`${config.costConfirmationRequired ?? true}\` | Demande confirmation si le seuil d'alerte est dépassé | \`/poll config costConfirmation <true/false>\` |
411
+ | **costThreshold**| \`${config.costThreshold ?? 0.15} 🌻\` | Seuil d'alerte coût Outils | \`/poll config costThreshold <X>\` |
412
+ | **cost_estimator**| \`${config.costEstimator ?? true}\` | Afficher l'estimation de coût dans les Toasts | \`/poll config cost_estimator <true/false>\` |
413
+ | **fallbacks.free.main** | \`${config.fallbacks?.free?.main || 'free/mistral'}\` | Modèle de repli Chat vers l'univers free legacy | \`/pollinations fallback <main> <agent>\` |
414
+ | **fallbacks.free.agent** | \`${config.fallbacks?.free?.agent || 'free/openai-fast'}\`| Modèle de repli Agent vers l'univers free legacy | \`/pollinations fallback <main> <agent>\` |
415
+ | **fallbacks.enter.agent** | \`${config.fallbacks?.enter?.agent || 'free/openai-fast'}\`| Modèle Agent principal (free/* ou enter/*) | *Géré automatiquement* |
416
+ | **status_gui** | \`${config.gui?.status || 'all'}\` | Toasts de statut (all, alert, none) | \`/poll config status_gui <all/alert/none>\` |
417
+ | **logs_gui** | \`${config.gui?.logs || 'error'}\` | Niveau de log (verbose, error) | \`/poll config logs_gui <verbose/error/none>\` |
418
+ | **threshold_tier** | \`${config.thresholds?.tier || 80}%\` | Alerte limite Quotidienne Gratuite | \`/poll config threshold_tier <1-100>\` |
419
+ | **threshold_wallet** | \`${config.thresholds?.wallet || 80}%\` | Alerte baisse de Wallet Premium | \`/poll config threshold_wallet <1-100>\` |
420
+ | **status_bar** | \`${config.statusBar ?? true}\` | Affiche l'icône dans la barre de statut | \`/poll config status_bar <true/false>\` |`;
376
421
  return {
377
422
  handled: true,
378
- response: JSON.stringify(config, null, 2)
423
+ response: markdownResponse
379
424
  };
380
425
  }
381
426
  if (key === 'toast_verbosity' && value) {
@@ -415,65 +460,349 @@ function handleConfigCommand(args) {
415
460
  saveConfig({ thresholds: { ...config.thresholds, tier: threshold } });
416
461
  return { handled: true, response: `✅ threshold_tier = ${threshold}%` };
417
462
  }
418
- if (key === 'threshold_wallet_warn' && value) {
463
+ if (key === 'threshold_wallet' && value) {
419
464
  const threshold = parseInt(value);
420
465
  if (isNaN(threshold) || threshold < 0 || threshold > 100) {
421
466
  return { handled: true, error: 'Valeur entre 0 et 100 requise' };
422
467
  }
423
468
  const config = loadConfig();
424
- saveConfig({ thresholds: { ...config.thresholds, wallet_warn: threshold } });
425
- return { handled: true, response: `✅ threshold_wallet_warn = ${threshold}%` };
426
- }
427
- if (key === 'threshold_wallet_stop' && value) {
428
- const stopValue = parseFloat(value);
429
- if (isNaN(stopValue) || stopValue < 0) {
430
- return { handled: true, error: 'Valeur $ positive requise' };
431
- }
432
- const config = loadConfig();
433
- saveConfig({ thresholds: { ...config.thresholds, wallet_stop: stopValue } });
434
- return { handled: true, response: `✅ threshold_wallet_stop = $${stopValue}` };
469
+ saveConfig({ thresholds: { ...config.thresholds, wallet: threshold } });
470
+ return { handled: true, response: `✅ threshold_wallet = ${threshold}%` };
435
471
  }
436
472
  if (key === 'status_bar' && value) {
437
473
  const enabled = value === 'true';
438
474
  saveConfig({ statusBar: enabled });
439
475
  return { handled: true, response: `✅ status_bar = ${enabled}` };
440
476
  }
477
+ if (key === 'cost_estimator' && value) {
478
+ const enabled = value === 'true';
479
+ const config = loadConfig();
480
+ saveConfig({ ...config, costEstimator: enabled });
481
+ return { handled: true, response: `✅ cost_estimator = ${enabled}` };
482
+ }
483
+ if (key === 'enablePaidTools' && value) {
484
+ const enabled = value === 'true';
485
+ saveConfig({ enablePaidTools: enabled });
486
+ return { handled: true, response: `✅ enablePaidTools = ${enabled}${!enabled ? ' (wallet protection active)' : ''}` };
487
+ }
488
+ if (key === 'costThreshold' && value) {
489
+ const threshold = parseFloat(value);
490
+ if (isNaN(threshold) || threshold < 0) {
491
+ return { handled: true, error: 'Valeur numérique positive requise (en pollen). Ex: 0.15' };
492
+ }
493
+ saveConfig({ costThreshold: threshold });
494
+ return { handled: true, response: `✅ costThreshold = ${threshold} 🌻` };
495
+ }
496
+ if (key === 'costConfirmation' && value) {
497
+ const enabled = value === 'true';
498
+ saveConfig({ costConfirmationRequired: enabled });
499
+ return { handled: true, response: `✅ costConfirmationRequired = ${enabled}` };
500
+ }
441
501
  return {
442
502
  handled: true,
443
- error: `Clé inconnue: ${key}. Clés: status_gui, logs_gui, threshold_tier, threshold_wallet_warn, threshold_wallet_stop, status_bar`
503
+ error: `Clé inconnue: ${key}. Clés: status_gui, logs_gui, threshold_tier, threshold_wallet, status_bar, cost_estimator, enablePaidTools, costThreshold, costConfirmation`
444
504
  };
445
505
  }
446
506
  function handleHelpCommand() {
447
507
  const help = `
448
- ### 🌸 Pollinations Plugin - Commandes V6 (Economy/Pro)
508
+ ### 🌸 Pollinations Plugin - Commandes V6
449
509
 
450
- - **\`/pollinations mode [mode]\`**: Change le mode (economy, pro, manual).
510
+ **Mode & Usage**
511
+ - **\`/pollinations mode [mode]\`**: Change le mode (manual, alwaysfree, pro).
451
512
  - **\`/pollinations usage [full]\`**: Affiche le dashboard (full = détail).
452
- - **\`/pollinations fallback [mode] [model]\`**: Configure le Safety Net pour economy/pro.
513
+ - **\`/pollinations fallback <main> [agent]\`**: Configure le Safety Net.
514
+
515
+ **Configuration**
453
516
  - **\`/pollinations config [key] [value]\`**:
454
- - \`threshold_tier\`: 0-100 (% Alerte Tier).
455
- - \`threshold_wallet_warn\`: 0-100 (% Alerte Wallet Session).
456
- - \`threshold_wallet_stop\`: Montage absolu $ (Stop Wallet).
457
- - \`status_gui\`: none, alert, all.
458
- - \`logs_gui\`: none, error, verbose.
459
- - \`status_bar\`: true/false (Widget).
517
+ - \`status_gui\`: none, alert, all
518
+ - \`logs_gui\`: none, error, verbose
519
+ - \`threshold_tier\` / \`threshold_wallet\`: 0-100
520
+ - \`status_bar\`: true/false
521
+ - \`cost_estimator\`: true/false (show cost in outputs)
522
+ - \`enablePaidTools\`: true/false (wallet protection)
523
+ - \`costThreshold\`: seuil en pollen (défaut: 0.15)
524
+ - \`costConfirmation\`: true/false (confirmation coût)
525
+
526
+ **Modèles & Pricing**
527
+ - **\`/pollinations models [type]\`**: Liste des modèles (type: image, video, audio, text)
528
+ - **\`/pollinations pricing\`**: Tableau de pricing détaillé
529
+ - **\`/pollinations infos\`**: Explications sur les Tiers et le Pollen
530
+
531
+ > 💡 **RMBG keys**: Use the \`rmbg_keys\` tool (works with any model).
460
532
  `.trim();
461
533
  return { handled: true, response: help };
462
534
  }
535
+ // === MODELS & PRICING COMMANDS ===
536
+ function parseNameDesc(m) {
537
+ const fullDesc = m.description || m.name;
538
+ const parts = fullDesc.split(" - ");
539
+ if (parts.length > 1) {
540
+ return { nom: parts[0].trim(), desc: parts.slice(1).join(" - ").trim() };
541
+ }
542
+ return { nom: fullDesc, desc: "" };
543
+ }
544
+ export async function handleModelsCommand(args) {
545
+ const filter = args[0]; // optional: image, video, audio, text
546
+ if (!ModelRegistry.isReady()) {
547
+ return {
548
+ handled: true,
549
+ response: '⏳ Le registre des modèles est en cours de chargement. Réessayez dans quelques secondes.'
550
+ };
551
+ }
552
+ const sections = [];
553
+ // --- FETCH FREE UNIVERSE GITHUB/LEGACY MODELS ---
554
+ if (!filter || filter === 'text') {
555
+ try {
556
+ const freeRes = await fetch('https://text.pollinations.ai/models', { signal: AbortSignal.timeout(4000) });
557
+ if (freeRes.ok) {
558
+ const freeData = await freeRes.json();
559
+ sections.push('## 🎁 Modèles Free Universe (Communautaire / Legacy API)\n');
560
+ sections.push(`> *Bonus gratuit disponible sans clé via l'API \`text.pollinations.ai\`. Dédié au Chat textuel.*\n`);
561
+ sections.push('| Nom | Alias / ID | Description | Vision | Tools |');
562
+ sections.push('|-----|------------|-------------|--------|-------|');
563
+ for (const m of freeData) {
564
+ const desc = m.description || m.name;
565
+ const aliases = m.aliases ? m.aliases.join(', ') : m.name;
566
+ sections.push(`| \`${m.name}\` | ${aliases} | ${desc.substring(0, 40)} | ${m.vision ? '👁️' : '❌'} | ${m.tools ? '🛠️' : '❌'} |`);
567
+ }
568
+ sections.push('');
569
+ }
570
+ }
571
+ catch (e) {
572
+ sections.push('## 🎁 Modèles Free Universe\n*(⚠️ L\'API communautaire text.pollinations.ai semble temporairement indisponible)*\n');
573
+ }
574
+ }
575
+ sections.push('## 📋 Modèles Pollinations Enter (Principaux)\n');
576
+ const categories = [
577
+ { cat: 'image', emoji: '🎨', label: 'Image' },
578
+ { cat: 'video', emoji: '🎬', label: 'Video' },
579
+ { cat: 'audio', emoji: '🔊', label: 'Audio' },
580
+ { cat: 'text', emoji: '📝', label: 'Text' },
581
+ ];
582
+ for (const { cat, emoji, label } of categories) {
583
+ if (filter && filter !== cat)
584
+ continue;
585
+ const models = ModelRegistry.list(cat);
586
+ if (models.length === 0)
587
+ continue;
588
+ const sorted = [...models].sort((a, b) => a.name.localeCompare(b.name));
589
+ sections.push(`### ${emoji} ${label} (${models.length} modèles)\n`);
590
+ sections.push('| Nom | ID | Description | Capabilities | Input | Output |');
591
+ sections.push('|-----|----|-------------|--------------|-------|--------|');
592
+ for (const m of sorted) {
593
+ const { nom, desc } = parseNameDesc(m);
594
+ const badges = buildBadges(m);
595
+ const input = buildInputIcons(m);
596
+ const output = buildOutputCost(m);
597
+ sections.push(`| ${nom} | \`${m.name}\` | ${desc.substring(0, 40)} | ${badges} | ${input} | ${output} |`);
598
+ }
599
+ sections.push('');
600
+ }
601
+ sections.push('> **Capabilities** : 👁️ vision · 🧠 reasoning · 🎙️ audio in · 🔍 search · 🔊 audio out · 💻 code exec');
602
+ sections.push('> **Other** : 💎 PAID ONLY (Wallet direct) · 📏 Contexte API max');
603
+ return { handled: true, response: sections.join('\n') };
604
+ }
605
+ export async function handlePricingCommand() {
606
+ try {
607
+ const cp = require('child_process');
608
+ const path = require('path');
609
+ // Pointeur __dirname -> dist/server. Donc on cible scripts/pollinations_pricing.js
610
+ const scriptPath = path.join(__dirname, 'scripts', 'pollinations_pricing.js');
611
+ // Exécution locale via Node (sécurisé pour le bundle prod, pas de npx tsx)
612
+ const output = cp.execSync(`node "${scriptPath}"`, { encoding: 'utf-8', stdio: 'pipe' });
613
+ return { handled: true, response: output };
614
+ }
615
+ catch (e) {
616
+ return { handled: true, error: `Erreur lors de la récupération des prix: ${e.message}` };
617
+ }
618
+ }
619
+ // ─── Formatting Helpers for Models/Pricing ────────────────────────────────
620
+ function buildBadges(m) {
621
+ const f = [];
622
+ if (m.paid_only)
623
+ f.push('💎');
624
+ const allFlags = [...(m.input_modalities || []), ...(m.output_modalities || []), m.name];
625
+ if (m.supportsI2X)
626
+ allFlags.push("👁️");
627
+ const str = allFlags.join(" ").toLowerCase();
628
+ if (str.includes("image") || str.includes("👁️"))
629
+ f.push("👁️");
630
+ if (m.reasoning || str.includes("reasoning"))
631
+ f.push("🧠");
632
+ if (str.includes("audio") || str.includes("whisper") || str.includes("scribe") || str.includes("🎙️"))
633
+ f.push("🎙️");
634
+ if (str.includes("search") || str.includes("sonar") || str.includes("gemini"))
635
+ f.push("🔍");
636
+ if (m.output_modalities.includes("audio") || (m.voices && m.voices.length > 0) || str.includes("tts") || str.includes("music"))
637
+ f.push("🔊");
638
+ if (str.includes("coder") || str.includes("code") || str.includes("gemini"))
639
+ f.push("💻");
640
+ return f.filter((v, i, a) => a.indexOf(v) === i).join(" ");
641
+ }
642
+ function buildInputIcons(m) {
643
+ const icons = [];
644
+ if (m.input_modalities.includes('text'))
645
+ icons.push('📝');
646
+ if (m.input_modalities.includes('image'))
647
+ icons.push('🖼️');
648
+ if (m.input_modalities.includes('audio'))
649
+ icons.push('🎤');
650
+ return icons.join('') || '📝';
651
+ }
652
+ function buildOutputCost(m) {
653
+ const p = m.pricing;
654
+ if (p.completionImageTokens) {
655
+ return p.completionImageTokens < 0.0001
656
+ ? `~tokens`
657
+ : `${p.completionImageTokens} 🌻/img`;
658
+ }
659
+ if (p.completionVideoSeconds)
660
+ return `${p.completionVideoSeconds} 🌻/s`;
661
+ if (p.completionVideoTokens)
662
+ return `~tokens/s`;
663
+ if (p.completionAudioTokens)
664
+ return `${p.completionAudioTokens} 🌻/tok`;
665
+ if (p.completionAudioSeconds)
666
+ return `${p.completionAudioSeconds} 🌻/s`;
667
+ if (p.promptAudioSeconds)
668
+ return `${p.promptAudioSeconds} 🌻/s`;
669
+ if (p.completionTextTokens)
670
+ return `${p.completionTextTokens} 🌻/tok`;
671
+ return '~tokens';
672
+ }
673
+ export async function handleInfosCommand() {
674
+ const config = loadConfig();
675
+ let name = "Developer";
676
+ let tier = "anonymous";
677
+ if (config.apiKey) {
678
+ try {
679
+ const res = await fetch('https://gen.pollinations.ai/account/profile', {
680
+ headers: { 'Authorization': `Bearer ${config.apiKey}` }
681
+ });
682
+ if (res.ok) {
683
+ const data = await res.json();
684
+ if (data.name)
685
+ name = data.name;
686
+ tier = data.tier || "anonymous";
687
+ }
688
+ }
689
+ catch (e) {
690
+ // Ignorer l'erreur réseau et garder les valeurs par défaut
691
+ }
692
+ }
693
+ const emojis = {
694
+ microbe: '🦠', spore: '🍄', seed: '🌱', flower: '🌸', nectar: '🍯', anonymous: '👤'
695
+ };
696
+ const tierEmoji = emojis[tier] || '❓';
697
+ const response = `## 🍯💚 POLLINATIONS OPENCODE PLUGIN 💚🍯
698
+
699
+ Bienvenue **${name}** sur le plugin Pollinations pour OpenCode !
700
+
701
+ Ce plugin vous permet de générer du code, des images, d'analyser des vidéos et 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.
702
+
703
+ **Ce Que ce plugin vous apporte en plus ! :**
704
+
705
+ **🛠️ Outils Gratuits Intégrés (Toujours disponibles) :**
706
+ - \`gen_qrcode\` / \`gen_diagram\` / \`gen_palette\` : Outils visuels et dev.
707
+ - \`remove_background\` : Détourage d'image natif.
708
+ - \`extract_frames\` / \`extract_audio\` : Extraction rapide de contenu média.
709
+ - \`file_to_url\` : Hébergement instantané de vos fichiers locaux en ligne.
710
+
711
+ **💎 Outils Pollinations (Premium - Automatisés avec votre Clé) :**
712
+ - \`polli_gen_image\` : Génération d'images (Flux, Seedream, Gemini) + support Image-to-Image.
713
+ - \`polli_gen_video\` : Génération vidéo text-to-video / image-to-video (Veo, Wan, LTX...).
714
+ - \`polli_gen_audio\`/\`polli_stt\` : Transcription Whisper, Text-to-Speech ElevenLabs.
715
+ - \`polli_gen_music\` : Moteur de musique générative.
716
+ - \`polli_web_search\` : Recherche connectée pour étendre la base de contexte de l'agent.
717
+
718
+ - **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...
719
+
720
+ ---
721
+
722
+ > **Your tiers:** ${tierEmoji} ${tier.toUpperCase()}
723
+
724
+ ---
725
+
726
+ ## 🌍 Qu'est-ce que pollinations.ai ?
727
+ pollinations.ai est une plateforme d'IA open-source construite par et pour la communauté. Nous offrons une API unifiée pour les images, le texte, l'audio et la vidéo. Tout fonctionne de manière ouverte : notre code, notre feuille de route, nos conversations. Des centaines de développeurs construisent déjà des outils, des jeux, des bots et des expériences farfelues avec nous. Vous êtes les bienvenus !
728
+
729
+ Pas de boîtes noires. Pas de dépendance exclusive (vendor lock-in). Juste une API conviviale et un Discord rempli de personnes qui s'entraident réellement.
730
+
731
+ ---
732
+
733
+ ## 📈 Évoluez votre Palier (Tier)
734
+ Pour les développeurs qui créent avec pollinations.ai. Montez de niveau pour gagner plus de Pollen quotidien.
735
+
736
+ - 🦠 **Microbe** (0.1 pollen/jour) : Pour débloquer : S'inscrire
737
+ - 🍄 **Spore** (1 pollen/jour) : Pour débloquer : Vérification automatique (Vérifié à l'inscription)
738
+ - 🌱 **Seed** (3 pollen/jour) : Pour débloquer : 8+ points dev (Mise à niveau automatique hebdomadaire)
739
+ - 🌸 **Flower** (10 pollen/jour) : Pour débloquer : Publier une application (🌱 Doit être Seed en premier)
740
+ - 🍯 **Nectar** (20 pollen/jour) : Bientôt disponible 🔮
741
+
742
+ ✨ *Nous sommes en bêta ! Nous apprenons ce qui fonctionne le mieux pour notre communauté et pourrons ajuster les valeurs de pollen et les règles des paliers. Merci de faire partie de cette aventure !*
743
+
744
+ ---
745
+
746
+ ## 💎 Qu'est-ce que le Pollen ?
747
+ Faire tourner des modèles d'IA coûte de l'argent. Le Pollen est notre moyen de faire fonctionner les serveurs sans publicité ni revente de vos données. Un crédit simple et unique pour tous les modèles — prévisible, transparent, sans surprises.
748
+
749
+ **$1 ≈ 1 Pollen** (les prix peuvent évoluer). Vous le dépensez pour faire des appels API.
750
+
751
+ ## 🛒 Comment obtenir du Pollen ?
752
+ Il y a trois moyens d'ajouter du Pollen à votre solde :
753
+
754
+ 1. **L'acheter** : Achetez des packs de Pollen directement par carte bancaire. Ce Pollen va dans votre portefeuille (wallet) et n'expire jamais. Des packs simples, pas d'abonnements, pas de paliers bloquants.
755
+ 2. **Pollen Quotidien** : Pendant et après la bêta, les développeurs inscrits reçoivent des subventions quotidiennes de Pollen pour soutenir leurs expérimentations, en fonction de leur palier (microbe, spore, seed, flower, nectar).
756
+ 3. **Le Gagner** : Complétez des récompenses communautaires ponctuelles (comme aider à résoudre un problème technique ou contribuer au projet). Chaque contribution vous fait gagner du Pollen. Nous le remarquons et nous partageons.
757
+
758
+ ---
759
+
760
+ ### 💡 Comment le Pollen est-il dépensé ?
761
+ 1. **Les subventions quotidiennes (Tier grants)** sont utilisées en premier.
762
+ 2. **Le Pollen acheté (Wallet)** est utilisé une fois le Pollen quotidien épuisé.
763
+ ⚠️ **Exception** : 💎 Les modèles *Paid Only* (ex: claude-large, veo, seedream-pro) requièrent uniquement du Pollen acheté.`;
764
+ return { handled: true, response };
765
+ }
463
766
  // === INTEGRATION OPENCODE ===
464
767
  export function createCommandHooks() {
465
768
  return {
466
769
  'tui.command.execute': async (input, output) => {
467
- const result = await handleCommand(input.command);
468
- if (result.handled) {
469
- output.handled = true;
470
- if (result.response) {
471
- output.response = result.response;
472
- }
473
- if (result.error) {
474
- output.error = result.error;
770
+ if (!input.command.startsWith('/pollinations')) {
771
+ return;
772
+ }
773
+ try {
774
+ // Parse command
775
+ const rawArgs = input.command.replace('/pollinations', '').trim();
776
+ const result = await handleCommand(rawArgs);
777
+ if (result.handled) {
778
+ if (result.error) {
779
+ output.error = `❌ **Erreur:** ${result.error}`;
780
+ }
781
+ else if (result.response) {
782
+ output.response = result.response;
783
+ }
784
+ // If no response and no error, assume handled silently (like appendPrompt)
475
785
  }
476
786
  }
787
+ catch (err) {
788
+ output.error = `❌ **Erreur Critique:** ${err.message}`;
789
+ }
790
+ },
791
+ // Hook for UI Commands (Palette / Buttons)
792
+ 'command.execute.before': async (input, output) => {
793
+ const cmd = input.command;
794
+ if (cmd === 'pollinations.addKey') {
795
+ handleCommand('addKey'); // Return help message
796
+ }
797
+ else if (cmd === 'pollinations.usage') {
798
+ const res = await handleCommand('usage');
799
+ if (res.response)
800
+ globalClient?.tui.showToast({ title: "Pollinations Usage", metadata: { type: 'info', message: "Voir logs pour usage détaillé" } });
801
+ }
802
+ else if (cmd === 'pollinations.mode') {
803
+ // UI Pollution Fix: SILENCE.
804
+ // User explicitly requested NO messages.
805
+ }
477
806
  }
478
807
  };
479
808
  }