opencode-pollinations-plugin 6.3.0 → 6.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +3 -3
- package/README.es.md +3 -3
- package/README.fr.md +3 -3
- package/README.it.md +3 -3
- package/README.md +2 -2
- package/README.zh.md +3 -3
- package/dist/locales/de.json +3 -1
- package/dist/locales/en.json +3 -1
- package/dist/locales/es.json +3 -1
- package/dist/locales/fr.json +3 -1
- package/dist/locales/it.json +3 -1
- package/dist/locales/zh.json +3 -1
- package/dist/server/commands.js +83 -2
- package/dist/server/config.d.ts +2 -0
- package/dist/server/quota.js +134 -73
- package/dist/server/tier-info.js +6 -6
- package/dist/tools/pollinations/polli_config.js +5 -1
- package/package.json +1 -1
package/README.de.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# 🌸 Pollinations AI Plugin für OpenCode (v6.
|
|
1
|
+
# 🌸 Pollinations AI Plugin für OpenCode (v6.4)
|
|
2
2
|
|
|
3
|
-
## ✨ Neu in v6.
|
|
3
|
+
## ✨ Neu in v6.4.0
|
|
4
4
|
|
|
5
5
|
- 🎯 **Quests & Gamification** : `polli_quests` + `/poll quests` — sieh deine Quests und das **einlösbare kostenlose Pollen**. Die Nutzung des Plugins schließt Quests rückwirkend ab!
|
|
6
6
|
- 🆓 **Kostenlose Bild- & Videogenerierung (ohne Schlüssel!)** : `gen_edit_image_free` (erstellen + bearbeiten, ~20/Tag) und `gen_video_free` (Text→Video + Bild/Audio, ~5/Tag), für jedes Modell.
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
38
|
-
## ✨ Was ist neu in V6.
|
|
38
|
+
## ✨ Was ist neu in V6.4?
|
|
39
39
|
|
|
40
40
|
- ⏱️ **Stündliche Quoten**: Verabschieden Sie sich von täglichen Limits! Developer Tiers werden jetzt **jede Stunde** pünktlich um `:00` zurückgesetzt. So haben Sie bei langen Code-Sitzungen immer frische Credits.
|
|
41
41
|
- ⚡ **100% Dynamische Engine**: Keine hartcodierten Modelllisten, festen Standardkonfigurationen und Preise mehr! In V6.2 ruft der KI-Agent von OpenCode dynamisch die neuesten LLMs, Parameter, Tags (`[💎 Paid]`, `[🌿 Free]`) und Kostenabschätzungen über die Pollinations-APIs ab.
|
package/README.es.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# 🌸 Pollinations AI Plugin para OpenCode (v6.
|
|
1
|
+
# 🌸 Pollinations AI Plugin para OpenCode (v6.4)
|
|
2
2
|
|
|
3
|
-
## ✨ Novedades v6.
|
|
3
|
+
## ✨ Novedades v6.4.0
|
|
4
4
|
|
|
5
5
|
- 🎯 **Misiones y Gamificación** : `polli_quests` + `/poll quests` — mira tus misiones y el **Pollen gratis por reclamar**. ¡Usar el plugin completa misiones retroactivamente!
|
|
6
6
|
- 🆓 **Imagen y Vídeo gratis (¡sin clave!)** : `gen_edit_image_free` (generar + editar, ~20/día) y `gen_video_free` (texto→vídeo + imagen/audio, ~5/día), para cualquier modelo.
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
38
|
-
## ✨ Novedades de la V6.
|
|
38
|
+
## ✨ Novedades de la V6.4
|
|
39
39
|
|
|
40
40
|
- ⏱️ **Cuotas por Hora**: ¡Dile adiós a los límites diarios! Los Tiers de Desarrollador se recargan **cada hora** exacta (a la hora en punto `:00`), asegurando que siempre tengas créditos frescos para tus largas jornadas de código.
|
|
41
41
|
- ⚡ **Motor 100% Dinámico**: ¡Se acabaron las listas de modelos quemadas en el código y los precios estáticos! En V6.2, el agente obtiene dinámicamente los últimos LLMs, parámetros, etiquetas (`[💎 Paid]`, `[🌿 Free]`) y costes aproximados conectándose continuamente a la API de Pollinations.
|
package/README.fr.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# 🌸 Plugin Pollinations AI pour OpenCode (v6.
|
|
1
|
+
# 🌸 Plugin Pollinations AI pour OpenCode (v6.4)
|
|
2
2
|
|
|
3
|
-
## ✨ Nouveautés v6.
|
|
3
|
+
## ✨ Nouveautés v6.4.0
|
|
4
4
|
|
|
5
5
|
- 🎯 **Quêtes & Gamification** : `polli_quests` + `/poll quests` — voyez vos quêtes et le **Pollen gratuit à réclamer**. Utiliser le plugin complète des quêtes rétroactivement !
|
|
6
6
|
- 🆓 **Image & Vidéo gratuites (sans clé !)** : `gen_edit_image_free` (génération + édition, ~20/j) et `gen_video_free` (texte→vidéo + image/audio, ~5/j), pour n'importe quel modèle.
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
38
|
-
## ✨ Nouveautés de la V6.
|
|
38
|
+
## ✨ Nouveautés de la V6.4
|
|
39
39
|
|
|
40
40
|
- ⏱️ **Quotas Horaires** : Dites adieu aux limites journalières ! Les Tiers Développeurs se réinitialisent désormais **toutes les heures** pile (à `:00`), vous assurant d'avoir toujours des crédits frais disponibles pendant vos longues sessions de code.
|
|
41
41
|
- ⚡ **Moteur 100% Dynamique** : Fini les listes de modèles codées en dur, les configurations par défaut et les prix fixes ! L'agent IA récupère dynamiquement les derniers LLMs, paramètres, tags (`[💎 Paid]`, `[🌿 Free]`) et coûts approximatifs depuis les API Pollinations.
|
package/README.it.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# 🌸 Pollinations AI Plugin per OpenCode (v6.
|
|
1
|
+
# 🌸 Pollinations AI Plugin per OpenCode (v6.4)
|
|
2
2
|
|
|
3
|
-
## ✨ Novità v6.
|
|
3
|
+
## ✨ Novità v6.4.0
|
|
4
4
|
|
|
5
5
|
- 🎯 **Quest e Gamification** : `polli_quests` + `/poll quests` — guarda le tue quest e il **Pollen gratuito da riscattare**. Usare il plugin completa quest retroattivamente!
|
|
6
6
|
- 🆓 **Immagini e Video gratis (senza chiave!)** : `gen_edit_image_free` (genera + modifica, ~20/giorno) e `gen_video_free` (testo→video + immagine/audio, ~5/giorno), per qualsiasi modello.
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
38
|
-
## ✨ Novità della V6.
|
|
38
|
+
## ✨ Novità della V6.4
|
|
39
39
|
|
|
40
40
|
- ⏱️ **Quote Orarie**: Addio ai limiti giornalieri! I Tier Sviluppatore ora si ricaricano **ogni singola ora** in punto (`:00`), garantendoti sempre crediti freschi durante le tue sessioni di programmazione.
|
|
41
41
|
- ⚡ **Motore 100% Dinamico**: Fine delle liste di modelli hard-coded, delle configurazioni fisse e dei prezzi statici! Nella V6.2, l'agente AI di OpenCode recupera dinamicamente gli ultimi LLM, parametri, tag (`[💎 Paid]`, `[🌿 Free]`) e stime dei costi dalle API di Pollinations.
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# 🌸 Pollinations AI Plugin for OpenCode (v6.
|
|
1
|
+
# 🌸 Pollinations AI Plugin for OpenCode (v6.4)
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
<img src="https://avatars.githubusercontent.com/u/88394740?s=400&v=4" alt="Pollinations.ai Logo" width="180">
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
-
## ✨ What's new in
|
|
30
|
+
## ✨ What's new in v6.4?
|
|
31
31
|
|
|
32
32
|
- 🎯 **Quests & Gamification**: New `polli_quests` tool + `/poll quests` command. See your Pollinations quests by category and the **free Pollen waiting to be claimed**. Just using this plugin (text, image & audio models) completes several quests **retroactively** — you may already have free Pollen waiting!
|
|
33
33
|
- 🆓 **Free Image & Video Tools (no key required!)**: Two brand-new bonus tools usable by **any** OpenCode model, even without a Pollinations account:
|
package/README.zh.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# 🌸 Pollinations AI 插件 - OpenCode版 (v6.
|
|
1
|
+
# 🌸 Pollinations AI 插件 - OpenCode版 (v6.4)
|
|
2
2
|
|
|
3
|
-
## ✨ v6.
|
|
3
|
+
## ✨ v6.4.0 新功能
|
|
4
4
|
|
|
5
5
|
- 🎯 **任务与游戏化** :`polli_quests` + `/poll quests` — 查看你的任务和**可领取的免费 Pollen**。使用本插件会追溯性地完成多个任务!
|
|
6
6
|
- 🆓 **免费图像和视频 (无需密钥!)** :`gen_edit_image_free` (生成 + 编辑,约 20/天) 和 `gen_video_free` (文本→视频 + 图像/音频,约 5/天),适用于任何模型。
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
38
|
-
## ✨ V6.
|
|
38
|
+
## ✨ V6.4 新篇章
|
|
39
39
|
|
|
40
40
|
- ⏱️ **小时级额度刷新**: 告别漫长的每日限额等待!现有的开发者等级现在将在 **每个整点** (`:00`) 清零并重新下放属于你的花粉(Pollen)。这确保您的代码生成和创意测试不被中断!
|
|
41
41
|
- ⚡ **100% 动态云端引擎**: 在 V6.2 中,代理助手如今能够自主向 Pollinations API 前台发送查询请求,自动读取并注入全网最新的 AI 模型表 (`[💎 Paid]`, `[🌿 Free]`) 、动态最高限额与实时期望消费数据,硬编码的时代宣告结束。
|
package/dist/locales/de.json
CHANGED
|
@@ -62,7 +62,9 @@
|
|
|
62
62
|
"table_head2": "|--------|-------|--------|--------|\n",
|
|
63
63
|
"no_history": "*Keine Aktivität in diesem Zeitraum.*\n",
|
|
64
64
|
"full_requires_key": "⚠️ `/poll usage full` erfordert einen API-Schlüssel.\n",
|
|
65
|
-
"hint_full": "💡 Verwenden Sie `/poll usage full` für Details."
|
|
65
|
+
"hint_full": "💡 Verwenden Sie `/poll usage full` für Details.",
|
|
66
|
+
"source_split": "- **Diese Periode verbraucht:** 🎁 Quest {tier} · 💎 Bezahlt {pack}\n",
|
|
67
|
+
"split": "- **Guthaben:** {total} 🌻 · 🎁 Quest ~{quest} · 💎 Bezahlt ~{paid}\n\n"
|
|
66
68
|
},
|
|
67
69
|
"fallback": {
|
|
68
70
|
"current": "Current fallbacks:\n- {free}\n- {enter}",
|
package/dist/locales/en.json
CHANGED
|
@@ -62,7 +62,9 @@
|
|
|
62
62
|
"table_head2": "|-------|------|------|--------|\n",
|
|
63
63
|
"no_history": "*No activity in the current period.*\n",
|
|
64
64
|
"full_requires_key": "⚠️ `/poll usage full` requires a connected API key.\n",
|
|
65
|
-
"hint_full": "💡 Use `/poll usage full` for details (requires key)."
|
|
65
|
+
"hint_full": "💡 Use `/poll usage full` for details (requires key).",
|
|
66
|
+
"source_split": "- **Consumed this period:** 🎁 Quest {tier} · 💎 Paid {pack}\n",
|
|
67
|
+
"split": "- **Balance:** {total} 🌻 · 🎁 Quest ~{quest} · 💎 Paid ~{paid}\n\n"
|
|
66
68
|
},
|
|
67
69
|
"fallback": {
|
|
68
70
|
"current": "Current fallbacks:\n- {free}\n- {enter}",
|
package/dist/locales/es.json
CHANGED
|
@@ -62,7 +62,9 @@
|
|
|
62
62
|
"table_head2": "|--------|--------|-------|--------|\n",
|
|
63
63
|
"no_history": "*Sin actividad en el período actual.*\n",
|
|
64
64
|
"full_requires_key": "⚠️ `/poll usage full` requiere una clave API conectada.\n",
|
|
65
|
-
"hint_full": "💡 Usa `/poll usage full` para ver detalles (requiere clave)."
|
|
65
|
+
"hint_full": "💡 Usa `/poll usage full` para ver detalles (requiere clave).",
|
|
66
|
+
"source_split": "- **Consumido este período:** 🎁 Misiones {tier} · 💎 Pagado {pack}\n",
|
|
67
|
+
"split": "- **Saldo:** {total} 🌻 · 🎁 Misiones ~{quest} · 💎 Pagado ~{paid}\n\n"
|
|
66
68
|
},
|
|
67
69
|
"fallback": {
|
|
68
70
|
"current": "Current fallbacks:\n- {free}\n- {enter}",
|
package/dist/locales/fr.json
CHANGED
|
@@ -62,7 +62,9 @@
|
|
|
62
62
|
"table_head2": "|--------|------|------|--------|\n",
|
|
63
63
|
"no_history": "*Aucune activité dans la période en cours.*\n",
|
|
64
64
|
"full_requires_key": "⚠️ `/poll usage full` nécessite une clé API connectée.\n",
|
|
65
|
-
"hint_full": "💡 Utilisez `/poll usage full` pour les détails (nécessite une clé)."
|
|
65
|
+
"hint_full": "💡 Utilisez `/poll usage full` pour les détails (nécessite une clé).",
|
|
66
|
+
"source_split": "- **Consommé cette période :** 🎁 Quêtes {tier} · 💎 Payé {pack}\n",
|
|
67
|
+
"split": "- **Solde :** {total} 🌻 · 🎁 Quêtes ~{quest} · 💎 Payé ~{paid}\n\n"
|
|
66
68
|
},
|
|
67
69
|
"fallback": {
|
|
68
70
|
"current": "Fallbacks actuels:\n- {free}\n- {enter}",
|
package/dist/locales/it.json
CHANGED
|
@@ -62,7 +62,9 @@
|
|
|
62
62
|
"table_head2": "|---------|-------|-------|-------|\n",
|
|
63
63
|
"no_history": "*Nessuna attività in questo periodo.*\n",
|
|
64
64
|
"full_requires_key": "⚠️ `/poll usage full` richiede una chiave API.\n",
|
|
65
|
-
"hint_full": "💡 Usa `/poll usage full` per i dettagli."
|
|
65
|
+
"hint_full": "💡 Usa `/poll usage full` per i dettagli.",
|
|
66
|
+
"source_split": "- **Consumato questo periodo:** 🎁 Quest {tier} · 💎 Pagato {pack}\n",
|
|
67
|
+
"split": "- **Saldo:** {total} 🌻 · 🎁 Quest ~{quest} · 💎 Pagato ~{paid}\n\n"
|
|
66
68
|
},
|
|
67
69
|
"fallback": {
|
|
68
70
|
"current": "Current fallbacks:\n- {free}\n- {enter}",
|
package/dist/locales/zh.json
CHANGED
|
@@ -62,7 +62,9 @@
|
|
|
62
62
|
"table_head2": "|-------|------|------|--------|\n",
|
|
63
63
|
"no_history": "*当前周期内无活动。*\n",
|
|
64
64
|
"full_requires_key": "⚠️ `/poll usage full` 需要已连接的 API 密钥。\n",
|
|
65
|
-
"hint_full": "💡 使用 `/poll usage full` 查看详情 (需要密钥)。"
|
|
65
|
+
"hint_full": "💡 使用 `/poll usage full` 查看详情 (需要密钥)。",
|
|
66
|
+
"source_split": "- **本期已消耗:** 🎁 任务 {tier} · 💎 付费 {pack}\n",
|
|
67
|
+
"split": "- **余额:** {total} 🌻 · 🎁 任务 ~{quest} · 💎 付费 ~{paid}\n\n"
|
|
66
68
|
},
|
|
67
69
|
"fallback": {
|
|
68
70
|
"current": "当前回退:\n- {free}\n- {enter}",
|
package/dist/server/commands.js
CHANGED
|
@@ -7,6 +7,27 @@ import { ModelRegistry } from './models/index.js';
|
|
|
7
7
|
import { t } from '../locales/index.js';
|
|
8
8
|
import { formatTierTable } from './tier-info.js';
|
|
9
9
|
import { buildQuestsReport } from '../tools/pollinations/polli_quests.js';
|
|
10
|
+
// GET helper for /account/* JSON endpoints (used for quest-reward reconstruction).
|
|
11
|
+
function fetchAccountJson(path, apiKey) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const req = https.request({
|
|
14
|
+
hostname: 'gen.pollinations.ai', path, method: 'GET',
|
|
15
|
+
headers: { 'Authorization': `Bearer ${apiKey}`, 'User-Agent': 'opencode-pollinations-plugin' },
|
|
16
|
+
}, (res) => {
|
|
17
|
+
let data = '';
|
|
18
|
+
res.on('data', c => data += c);
|
|
19
|
+
res.on('end', () => { try {
|
|
20
|
+
resolve(JSON.parse(data));
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
reject(e);
|
|
24
|
+
} });
|
|
25
|
+
});
|
|
26
|
+
req.on('error', reject);
|
|
27
|
+
req.setTimeout(10000, () => { req.destroy(); reject(new Error('Timeout')); });
|
|
28
|
+
req.end();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
10
31
|
function checkEndpoint(ep, key) {
|
|
11
32
|
return new Promise((resolve) => {
|
|
12
33
|
const req = https.request({
|
|
@@ -241,7 +262,48 @@ export async function handleUsageCommand(args) {
|
|
|
241
262
|
response += t('commands.usage.tier', { emoji: quota.tierEmoji, tier: quota.tier.toUpperCase(), limit: quota.tierLimit });
|
|
242
263
|
response += t('commands.usage.quota', { remaining: formatPollen(quota.tierLimit - quota.tierRemaining), limit: formatPollen(quota.tierLimit) });
|
|
243
264
|
response += t('commands.usage.usage_bar', { bar: progressBar(quota.tierLimit - quota.tierRemaining, quota.tierLimit) });
|
|
244
|
-
|
|
265
|
+
// Reconstructed Quest/Paid split by CROSS-REFERENCING exact data (no magic field needed):
|
|
266
|
+
// • claimed quest rewards (tier bucket) ← /account/quests (exact)
|
|
267
|
+
// • tier consumption since the first claim ← /account/usage meter_source=='tier' (exact)
|
|
268
|
+
// • current-hour floor remaining (≤ tierLimit) ← quota (the only fuzzy term, ignored)
|
|
269
|
+
// Quest remaining ≈ claimedQuestTier − tierConsumedSinceClaim (+ floor); Paid = Total − Quest.
|
|
270
|
+
const total = quota.tierRemaining + quota.walletBalance; // true balance
|
|
271
|
+
let claimedQuestTier = 0;
|
|
272
|
+
let firstClaimMs = Infinity;
|
|
273
|
+
let tierConsumedSinceClaim = 0;
|
|
274
|
+
if (config.apiKey && config.keyHasAccessToProfile !== false) {
|
|
275
|
+
try {
|
|
276
|
+
const qres = await fetchAccountJson('/account/quests', config.apiKey);
|
|
277
|
+
for (const q of (qres?.quests || [])) {
|
|
278
|
+
const r = q.reward;
|
|
279
|
+
if (r && r.claimedAt && r.balanceBucket === 'tier') {
|
|
280
|
+
claimedQuestTier += (r.pollenAmount || 0);
|
|
281
|
+
const cms = new Date(r.claimedAt).getTime();
|
|
282
|
+
if (!isNaN(cms) && cms < firstClaimMs)
|
|
283
|
+
firstClaimMs = cms;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Sum tier-metered spend since the first claim (what actually ate the Quest stash).
|
|
287
|
+
if (claimedQuestTier > 0 && isFinite(firstClaimMs)) {
|
|
288
|
+
const ures = await fetchAccountJson('/account/usage?limit=100', config.apiKey);
|
|
289
|
+
for (const e of (ures?.usage || [])) {
|
|
290
|
+
const ts = new Date(String(e.timestamp).replace(' ', 'T') + (String(e.timestamp).includes('Z') ? '' : 'Z')).getTime();
|
|
291
|
+
if (e.meter_source === 'tier' && !isNaN(ts) && ts >= firstClaimMs) {
|
|
292
|
+
tierConsumedSinceClaim += (e.cost_usd || 0);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch { /* fall back gracefully if unreachable */ }
|
|
298
|
+
}
|
|
299
|
+
// Quest stash = claimed − consumed, plus the current hourly floor still available.
|
|
300
|
+
const questPollen = Math.max(0, claimedQuestTier - tierConsumedSinceClaim) + quota.tierRemaining;
|
|
301
|
+
const paidPollen = Math.max(0, total - questPollen);
|
|
302
|
+
response += t('commands.usage.split', {
|
|
303
|
+
quest: questPollen.toFixed(2),
|
|
304
|
+
paid: paidPollen.toFixed(2),
|
|
305
|
+
total: total.toFixed(2),
|
|
306
|
+
});
|
|
245
307
|
response += t('commands.usage.reset', { date: resetDate, duration: durationStr });
|
|
246
308
|
if (isFull && config.apiKey) {
|
|
247
309
|
if (config.keyHasAccessToProfile === false) {
|
|
@@ -254,6 +316,12 @@ export async function handleUsageCommand(args) {
|
|
|
254
316
|
const stats = calculateCurrentPeriodStats(usageData, lastReset, quota.tierLimit);
|
|
255
317
|
response += t('commands.usage.period_detail', { time: lastReset.toLocaleTimeString() });
|
|
256
318
|
response += t('commands.usage.total_reqs', { reqs: stats.totalRequests, inTok: formatTokens(stats.inputTokens), outTok: formatTokens(stats.outputTokens) });
|
|
319
|
+
// Exact consumption split by meter_source (tier = Quest Pollen, pack = Paid).
|
|
320
|
+
// This is the ONLY reliable split the API exposes (remaining split is dashboard/cookie-only).
|
|
321
|
+
response += t('commands.usage.source_split', {
|
|
322
|
+
tier: formatPollen(stats.tierUsed),
|
|
323
|
+
pack: formatPollen(stats.packUsed),
|
|
324
|
+
});
|
|
257
325
|
response += t('commands.usage.table_head1');
|
|
258
326
|
response += t('commands.usage.table_head2');
|
|
259
327
|
const sorted = Array.from(stats.models.entries()).sort((a, b) => b[1].cost - a[1].cost);
|
|
@@ -587,6 +655,7 @@ ${t('commands.config.table_divider')}
|
|
|
587
655
|
| **costConfirmationRequired**| \`${config.costConfirmationRequired ?? true}\` | ${t('commands.config.costConfirmationRequired_role')} | \`/poll config costConfirmationRequired <true/false>\` |
|
|
588
656
|
| **costThreshold**| \`${config.costThreshold ?? 0.15} 🌻\` | ${t('commands.config.costThreshold_role')} | \`/poll config costThreshold <X>\` |
|
|
589
657
|
| **cost_estimator**| \`${config.costEstimator ?? true}\` | ${t('commands.config.cost_estimator_role')} | \`/poll config cost_estimator <true/false>\` |
|
|
658
|
+
| **refillOverride**| \`${config.refillOverride ?? 'auto (déduit)'}\` | Quest Pollen refill horaire | \`/poll config refillOverride <0.01/0.15/0.4/0.8/10>\` |
|
|
590
659
|
| **fallbacks.free.main** | \`${config.fallbacks?.free?.main || 'free/mistral'}\` | ${t('commands.config.fallback_main_role')} | \`/poll fallback <main> <agent>\` |
|
|
591
660
|
| **fallbacks.free.agent** | \`${config.fallbacks?.free?.agent || 'free/openai-fast'}\`| ${t('commands.config.fallback_agent_role')} | \`/poll fallback <main> <agent>\` |
|
|
592
661
|
| **fallbacks.enter.agent** | \`${config.fallbacks?.enter?.agent || 'free/openai-fast'}\`| ${t('commands.config.fallback_enter_role')} | *${t('commands.config.managed_auto')}* |
|
|
@@ -683,9 +752,21 @@ ${t('commands.config.table_divider')}
|
|
|
683
752
|
saveConfig({ costConfirmationRequired: enabled });
|
|
684
753
|
return { handled: true, response: `✅ costConfirmationRequired = ${enabled}` };
|
|
685
754
|
}
|
|
755
|
+
if (key === 'refillOverride' && value) {
|
|
756
|
+
if (value === 'auto') {
|
|
757
|
+
saveConfig({ refillOverride: undefined });
|
|
758
|
+
return { handled: true, response: '✅ refillOverride = auto (déduction automatique)' };
|
|
759
|
+
}
|
|
760
|
+
const override = parseFloat(value);
|
|
761
|
+
if (isNaN(override) || ![0.01, 0.15, 0.4, 0.8, 10].includes(override)) {
|
|
762
|
+
return { handled: true, error: 'Valeurs supportées: auto, 0.01, 0.15, 0.4, 0.8, 10' };
|
|
763
|
+
}
|
|
764
|
+
saveConfig({ refillOverride: override });
|
|
765
|
+
return { handled: true, response: `✅ refillOverride = ${override} 🌻/h` };
|
|
766
|
+
}
|
|
686
767
|
return {
|
|
687
768
|
handled: true,
|
|
688
|
-
error: `Clé inconnue: ${key}. Clés: status_gui, logs_gui, threshold_tier, threshold_wallet, status_bar, cost_estimator, enablePaidTools, costThreshold, costConfirmationRequired, lang`
|
|
769
|
+
error: `Clé inconnue: ${key}. Clés: status_gui, logs_gui, threshold_tier, threshold_wallet, status_bar, cost_estimator, enablePaidTools, costThreshold, costConfirmationRequired, refillOverride, lang`
|
|
689
770
|
};
|
|
690
771
|
}
|
|
691
772
|
function handleHelpCommand() {
|
package/dist/server/config.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export interface PollinationsConfigV5 {
|
|
|
28
28
|
costConfirmationRequired: boolean;
|
|
29
29
|
statusBar: boolean;
|
|
30
30
|
costEstimator: boolean;
|
|
31
|
+
refillOverride?: number;
|
|
31
32
|
lang?: string;
|
|
32
33
|
}
|
|
33
34
|
export declare function loadConfig(): PollinationsConfigV5;
|
|
@@ -58,6 +59,7 @@ export declare function saveConfig(updates: Partial<PollinationsConfigV5>): {
|
|
|
58
59
|
costConfirmationRequired: boolean;
|
|
59
60
|
statusBar: boolean;
|
|
60
61
|
costEstimator: boolean;
|
|
62
|
+
refillOverride?: number;
|
|
61
63
|
lang?: string;
|
|
62
64
|
};
|
|
63
65
|
export declare function saveKeyToAuthJson(key: string): boolean;
|
package/dist/server/quota.js
CHANGED
|
@@ -1,65 +1,147 @@
|
|
|
1
|
-
import * as https from 'https';
|
|
1
|
+
import * as https from 'https';
|
|
2
2
|
import { loadConfig } from './config.js';
|
|
3
3
|
// === CACHE & CONSTANTS ===
|
|
4
|
-
const CACHE_TTL = 30000;
|
|
4
|
+
const CACHE_TTL = 30000;
|
|
5
5
|
let cachedQuota = null;
|
|
6
6
|
let lastQuotaFetch = 0;
|
|
7
7
|
const ONE_HOUR_MS = 60 * 60 * 1000;
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
spore: { pollen: 0.01, emoji: '🍄', cadence: 'hourly' },
|
|
17
|
-
seed: { pollen: 0.15, emoji: '🌱', cadence: 'hourly' },
|
|
18
|
-
flower: { pollen: 0.4, emoji: '🌸', cadence: 'hourly' },
|
|
19
|
-
nectar: { pollen: 0.8, emoji: '🍯', cadence: 'hourly' },
|
|
20
|
-
router: { pollen: 10, emoji: '🐝', cadence: 'hourly' },
|
|
21
|
-
};
|
|
8
|
+
const KNOWN_REFILLS = [
|
|
9
|
+
{ pollen: 0, emoji: '👤', label: 'anonymous' },
|
|
10
|
+
{ pollen: 0.01, emoji: '🍄', label: 'spore' },
|
|
11
|
+
{ pollen: 0.15, emoji: '🌱', label: 'seed' },
|
|
12
|
+
{ pollen: 0.4, emoji: '🌸', label: 'flower' },
|
|
13
|
+
{ pollen: 0.8, emoji: '🍯', label: 'nectar' },
|
|
14
|
+
{ pollen: 10, emoji: '🐝', label: 'router' },
|
|
15
|
+
];
|
|
22
16
|
// === LOGGING ===
|
|
23
17
|
import { logApi } from './logger.js';
|
|
24
18
|
function logQuota(msg) {
|
|
25
19
|
logApi(`[QUOTA] ${msg}`);
|
|
26
20
|
}
|
|
27
|
-
// === SMART FETCH API ===
|
|
21
|
+
// === SMART FETCH API (cursor-based) ===
|
|
28
22
|
export async function fetchUsageForPeriod(apiKey, lastReset) {
|
|
29
23
|
let allUsage = [];
|
|
30
|
-
|
|
31
|
-
|
|
24
|
+
const limit = 100;
|
|
25
|
+
let cursorEventId = null;
|
|
32
26
|
while (true) {
|
|
27
|
+
let queryPath = `/account/usage?limit=${limit}`;
|
|
28
|
+
if (cursorEventId) {
|
|
29
|
+
queryPath += `&before_event_id=${encodeURIComponent(cursorEventId)}`;
|
|
30
|
+
}
|
|
33
31
|
let usageRes;
|
|
34
32
|
try {
|
|
35
|
-
usageRes = await fetchAPI(
|
|
33
|
+
usageRes = await fetchAPI(queryPath, apiKey);
|
|
36
34
|
}
|
|
37
35
|
catch (e) {
|
|
38
|
-
logQuota(`SmartFetch failed
|
|
36
|
+
logQuota(`SmartFetch failed: ${e}`);
|
|
39
37
|
break;
|
|
40
38
|
}
|
|
41
39
|
if (!usageRes.usage || usageRes.usage.length === 0) {
|
|
42
|
-
break;
|
|
40
|
+
break;
|
|
43
41
|
}
|
|
44
42
|
let reachedCutoff = false;
|
|
45
43
|
for (const entry of usageRes.usage) {
|
|
46
|
-
const timestampStr = entry.timestamp.includes('Z')
|
|
44
|
+
const timestampStr = entry.timestamp.includes('Z')
|
|
45
|
+
? entry.timestamp
|
|
46
|
+
: entry.timestamp.replace(' ', 'T') + 'Z';
|
|
47
47
|
const entryTime = new Date(timestampStr);
|
|
48
48
|
if (entryTime < lastReset) {
|
|
49
49
|
reachedCutoff = true;
|
|
50
|
-
break;
|
|
50
|
+
break;
|
|
51
51
|
}
|
|
52
52
|
allUsage.push(entry);
|
|
53
53
|
}
|
|
54
|
-
// If we found an entry older than lastReset, or if the page was not full, we reached the end.
|
|
55
54
|
if (reachedCutoff || usageRes.usage.length < limit) {
|
|
56
55
|
break;
|
|
57
56
|
}
|
|
58
|
-
|
|
57
|
+
const lastEntry = usageRes.usage[usageRes.usage.length - 1];
|
|
58
|
+
cursorEventId = lastEntry.cursor_event_id || null;
|
|
59
|
+
if (!cursorEventId)
|
|
60
|
+
break;
|
|
59
61
|
}
|
|
60
62
|
logQuota(`SmartFetch: Retrieved ${allUsage.length} transactions for current period.`);
|
|
61
63
|
return allUsage;
|
|
62
64
|
}
|
|
65
|
+
// === ALLOWANCE DEDUCTION ===
|
|
66
|
+
async function fetchUsageForAllowance(apiKey) {
|
|
67
|
+
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * ONE_HOUR_MS);
|
|
68
|
+
const allUsage = [];
|
|
69
|
+
let cursorEventId = null;
|
|
70
|
+
let pageCount = 0;
|
|
71
|
+
const maxPages = 20;
|
|
72
|
+
while (pageCount < maxPages) {
|
|
73
|
+
let path = `/account/usage?days=7&limit=500`;
|
|
74
|
+
if (cursorEventId) {
|
|
75
|
+
path += `&before_event_id=${encodeURIComponent(cursorEventId)}`;
|
|
76
|
+
}
|
|
77
|
+
const res = await fetchAPI(path, apiKey);
|
|
78
|
+
if (!res.usage || res.usage.length === 0)
|
|
79
|
+
break;
|
|
80
|
+
let reachedCutoff = false;
|
|
81
|
+
for (const entry of res.usage) {
|
|
82
|
+
const ts = (entry.timestamp.includes('Z') ? entry.timestamp : entry.timestamp.replace(' ', 'T') + 'Z');
|
|
83
|
+
if (new Date(ts) < sevenDaysAgo) {
|
|
84
|
+
reachedCutoff = true;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
allUsage.push(entry);
|
|
88
|
+
}
|
|
89
|
+
if (reachedCutoff || res.usage.length < 500)
|
|
90
|
+
break;
|
|
91
|
+
const last = res.usage[res.usage.length - 1];
|
|
92
|
+
cursorEventId = last?.cursor_event_id || null;
|
|
93
|
+
if (!cursorEventId)
|
|
94
|
+
break;
|
|
95
|
+
pageCount++;
|
|
96
|
+
}
|
|
97
|
+
logQuota(`deduceAllowance: fetched ${allUsage.length} records over ${pageCount + 1} pages`);
|
|
98
|
+
return allUsage;
|
|
99
|
+
}
|
|
100
|
+
async function deduceAllowanceFromApi(apiKey) {
|
|
101
|
+
try {
|
|
102
|
+
const allUsage = await fetchUsageForAllowance(apiKey);
|
|
103
|
+
if (allUsage.length === 0)
|
|
104
|
+
return 0;
|
|
105
|
+
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * ONE_HOUR_MS);
|
|
106
|
+
const hourlyBuckets = new Map();
|
|
107
|
+
for (const entry of allUsage) {
|
|
108
|
+
if (entry.meter_source !== 'tier')
|
|
109
|
+
continue;
|
|
110
|
+
const ts = entry.timestamp.includes('Z') ? entry.timestamp : entry.timestamp.replace(' ', 'T') + 'Z';
|
|
111
|
+
const entryTime = new Date(ts);
|
|
112
|
+
if (entryTime < sevenDaysAgo)
|
|
113
|
+
continue;
|
|
114
|
+
const hourKey = entryTime.getUTCFullYear() * 1000000
|
|
115
|
+
+ entryTime.getUTCMonth() * 10000
|
|
116
|
+
+ entryTime.getUTCDate() * 100
|
|
117
|
+
+ entryTime.getUTCHours();
|
|
118
|
+
hourlyBuckets.set(hourKey, (hourlyBuckets.get(hourKey) || 0) + entry.cost_usd);
|
|
119
|
+
}
|
|
120
|
+
const maxHourlyTier = Math.max(0, ...hourlyBuckets.values());
|
|
121
|
+
const match = KNOWN_REFILLS.slice().reverse().find(r => r.pollen <= maxHourlyTier + 0.02);
|
|
122
|
+
logQuota(`deduceAllowance: ${allUsage.length} records, ${hourlyBuckets.size} hourly buckets, max=${maxHourlyTier.toFixed(4)}, deduced=${match?.pollen ?? 0} (${match?.label})`);
|
|
123
|
+
return match ? match.pollen : 0;
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
logQuota(`deduceAllowance failed: ${e}`);
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function deduceAllowanceFromUsage(usage) {
|
|
131
|
+
const tierCosts = usage
|
|
132
|
+
.filter(u => u.meter_source === 'tier')
|
|
133
|
+
.map(u => u.cost_usd);
|
|
134
|
+
const maxHourlyTier = Math.max(0, ...tierCosts);
|
|
135
|
+
const match = KNOWN_REFILLS.slice().reverse().find(r => r.pollen <= maxHourlyTier + 0.02);
|
|
136
|
+
return match ? match.pollen : 0;
|
|
137
|
+
}
|
|
138
|
+
function tierMetaForAllowance(allowance) {
|
|
139
|
+
const match = KNOWN_REFILLS.find(r => r.pollen === allowance)
|
|
140
|
+
|| KNOWN_REFILLS.findLast(r => r.pollen <= allowance);
|
|
141
|
+
return match
|
|
142
|
+
? { label: match.label, emoji: match.emoji }
|
|
143
|
+
: { label: 'unknown', emoji: '❓' };
|
|
144
|
+
}
|
|
63
145
|
// === MAIN QUOTA FUNCTION ===
|
|
64
146
|
export async function getQuotaStatus(forceRefresh = false) {
|
|
65
147
|
const config = loadConfig();
|
|
@@ -72,33 +154,29 @@ export async function getQuotaStatus(forceRefresh = false) {
|
|
|
72
154
|
}
|
|
73
155
|
try {
|
|
74
156
|
logQuota("Fetching Quota Data...");
|
|
75
|
-
// 1. Fetch API
|
|
76
|
-
// SEQUENTIAL FETCH (Avoid Rate Limits)
|
|
77
|
-
const profileRes = await fetchAPI('/account/profile', config.apiKey);
|
|
78
157
|
const balanceRes = await fetchAPI('/account/balance', config.apiKey);
|
|
79
|
-
const
|
|
80
|
-
const balance = balanceRes.balance;
|
|
81
|
-
// 2. Convertir Timezone : Obtenir instant exact du Reset
|
|
82
|
-
const resetInfo = calculateResetInfo(profile.nextResetAt);
|
|
83
|
-
logQuota(`Fetch Success. Tier: ${profile.tier}, Balance: ${balance}, Next Reset: ${profile.nextResetAt}`);
|
|
84
|
-
// 3. Smart Fetch : Récupérer uniquement les dépenses du jour (depuis lastReset)
|
|
158
|
+
const resetInfo = calculateResetInfo();
|
|
85
159
|
const periodUsage = await fetchUsageForPeriod(config.apiKey, resetInfo.lastReset);
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
160
|
+
const allowance = balanceRes.allowance
|
|
161
|
+
?? config.refillOverride
|
|
162
|
+
?? await deduceAllowanceFromApi(config.apiKey);
|
|
163
|
+
const tierMeta = tierMetaForAllowance(allowance);
|
|
164
|
+
const tierLimit = allowance;
|
|
89
165
|
const { tierUsed } = calculateCurrentPeriodUsage(periodUsage, resetInfo);
|
|
90
|
-
// 4. Calculate Balances
|
|
91
166
|
const tierRemaining = Math.max(0, tierLimit - tierUsed);
|
|
92
|
-
// Fix rounding errors
|
|
93
167
|
const cleanTierRemaining = Math.max(0, parseFloat(tierRemaining.toFixed(4)));
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
168
|
+
const totalBalance = balanceRes.total
|
|
169
|
+
?? balanceRes.balance
|
|
170
|
+
?? 0;
|
|
171
|
+
const paidPollenNative = balanceRes.pack;
|
|
172
|
+
const paidPollen = paidPollenNative !== undefined
|
|
173
|
+
? paidPollenNative
|
|
174
|
+
: Math.max(0, totalBalance - cleanTierRemaining);
|
|
175
|
+
const cleanWalletBalance = Math.max(0, parseFloat(paidPollen.toFixed(4)));
|
|
99
176
|
const tierAlertPercent = tierLimit > 0 ? (cleanTierRemaining / tierLimit * 100) : 0;
|
|
100
177
|
const tierNeedsAlert = tierLimit > 0 && tierAlertPercent <= config.thresholds.tier;
|
|
101
178
|
const walletNeedsAlert = cleanWalletBalance > 0 && cleanWalletBalance < (config.thresholds.wallet || 0.5);
|
|
179
|
+
logQuota(`Fetch Success. Allowance: ${allowance}, Paid: ${cleanWalletBalance}, Total: ${totalBalance}`);
|
|
102
180
|
cachedQuota = {
|
|
103
181
|
tierRemaining: cleanTierRemaining,
|
|
104
182
|
tierUsed,
|
|
@@ -109,8 +187,8 @@ export async function getQuotaStatus(forceRefresh = false) {
|
|
|
109
187
|
canUseEnterprise: cleanTierRemaining > 0.05 || cleanWalletBalance > 0.05,
|
|
110
188
|
isUsingWallet: cleanTierRemaining <= 0.05 && cleanWalletBalance > 0.05,
|
|
111
189
|
needsAlert: tierNeedsAlert || walletNeedsAlert,
|
|
112
|
-
tier:
|
|
113
|
-
tierEmoji:
|
|
190
|
+
tier: tierMeta.label,
|
|
191
|
+
tierEmoji: tierMeta.emoji
|
|
114
192
|
};
|
|
115
193
|
lastQuotaFetch = now;
|
|
116
194
|
return cachedQuota;
|
|
@@ -126,6 +204,7 @@ export async function getQuotaStatus(forceRefresh = false) {
|
|
|
126
204
|
}
|
|
127
205
|
}
|
|
128
206
|
function createDefaultQuota(tierName, limit) {
|
|
207
|
+
const meta = tierMetaForAllowance(limit);
|
|
129
208
|
return {
|
|
130
209
|
tierRemaining: 0,
|
|
131
210
|
tierUsed: 0,
|
|
@@ -136,8 +215,8 @@ function createDefaultQuota(tierName, limit) {
|
|
|
136
215
|
canUseEnterprise: false,
|
|
137
216
|
isUsingWallet: false,
|
|
138
217
|
needsAlert: false,
|
|
139
|
-
tier: tierName,
|
|
140
|
-
tierEmoji:
|
|
218
|
+
tier: tierName !== 'none' ? meta.label : 'none',
|
|
219
|
+
tierEmoji: meta.emoji
|
|
141
220
|
};
|
|
142
221
|
}
|
|
143
222
|
// === HELPERS ===
|
|
@@ -150,7 +229,7 @@ function fetchAPI(endpoint, apiKey) {
|
|
|
150
229
|
method: 'GET',
|
|
151
230
|
headers: {
|
|
152
231
|
'Authorization': `Bearer ${apiKey}`,
|
|
153
|
-
'User-Agent': 'opencode-pollinations-plugin/
|
|
232
|
+
'User-Agent': 'opencode-pollinations-plugin/6.4.1'
|
|
154
233
|
}
|
|
155
234
|
};
|
|
156
235
|
const req = https.request(options, (res) => {
|
|
@@ -176,26 +255,9 @@ function fetchAPI(endpoint, apiKey) {
|
|
|
176
255
|
req.end();
|
|
177
256
|
});
|
|
178
257
|
}
|
|
179
|
-
function calculateResetInfo(
|
|
258
|
+
function calculateResetInfo() {
|
|
180
259
|
const now = new Date();
|
|
181
|
-
|
|
182
|
-
// Guard also against malformed timestamps that would yield Invalid Date (NaN).
|
|
183
|
-
let nextReset = nextResetAt ? new Date(nextResetAt) : null;
|
|
184
|
-
if (!nextReset || isNaN(nextReset.getTime())) {
|
|
185
|
-
// No refill: there is no upcoming reset. Use a stable current-hour window
|
|
186
|
-
// so usage SmartFetch still has a sane lower bound, and surface 0 countdown.
|
|
187
|
-
const lastReset = new Date(now.getTime() - ONE_HOUR_MS);
|
|
188
|
-
return {
|
|
189
|
-
nextReset: now,
|
|
190
|
-
lastReset,
|
|
191
|
-
timeUntilReset: 0,
|
|
192
|
-
timeSinceReset: ONE_HOUR_MS,
|
|
193
|
-
resetHour: now.getUTCHours(),
|
|
194
|
-
resetMinute: now.getUTCMinutes(),
|
|
195
|
-
resetSecond: now.getUTCSeconds(),
|
|
196
|
-
progressPercent: 100
|
|
197
|
-
};
|
|
198
|
-
}
|
|
260
|
+
const nextReset = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours() + 1, 0, 0, 0));
|
|
199
261
|
const lastReset = new Date(nextReset.getTime() - ONE_HOUR_MS);
|
|
200
262
|
const timeUntilReset = Math.max(0, nextReset.getTime() - now.getTime());
|
|
201
263
|
const timeSinceReset = Math.max(0, now.getTime() - lastReset.getTime());
|
|
@@ -206,8 +268,8 @@ function calculateResetInfo(nextResetAt) {
|
|
|
206
268
|
timeUntilReset,
|
|
207
269
|
timeSinceReset,
|
|
208
270
|
resetHour: nextReset.getUTCHours(),
|
|
209
|
-
resetMinute:
|
|
210
|
-
resetSecond:
|
|
271
|
+
resetMinute: 0,
|
|
272
|
+
resetSecond: 0,
|
|
211
273
|
progressPercent
|
|
212
274
|
};
|
|
213
275
|
}
|
|
@@ -215,7 +277,6 @@ function calculateCurrentPeriodUsage(usage, resetInfo) {
|
|
|
215
277
|
let tierUsed = 0;
|
|
216
278
|
let packUsed = 0;
|
|
217
279
|
const entriesAfterReset = usage.filter(entry => {
|
|
218
|
-
// Safe Parse
|
|
219
280
|
const timestamp = entry.timestamp.replace(' ', 'T') + 'Z';
|
|
220
281
|
const entryTime = new Date(timestamp);
|
|
221
282
|
return entryTime >= resetInfo.lastReset;
|
|
@@ -241,5 +302,5 @@ export function formatQuotaForToast(quota) {
|
|
|
241
302
|
const hours = Math.floor(ms / (1000 * 60 * 60));
|
|
242
303
|
const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
|
|
243
304
|
const resetIn = `${hours}h${minutes}m`;
|
|
244
|
-
return `${quota.tierEmoji}
|
|
305
|
+
return `${quota.tierEmoji} Quest: ${quota.tierRemaining.toFixed(2)}/${quota.tierLimit} (${tierPercent}%) | 💎 Paid: $${quota.walletBalance.toFixed(2)} | ⏰ Reset: ${resetIn}`;
|
|
245
306
|
}
|
package/dist/server/tier-info.js
CHANGED
|
@@ -16,16 +16,16 @@ export const TIER_INFO = {
|
|
|
16
16
|
emoji: '🦠',
|
|
17
17
|
hourlyPollen: 0,
|
|
18
18
|
dailyEstimate: 0,
|
|
19
|
-
condition: '
|
|
20
|
-
conditionKey: 'tier.condition.
|
|
19
|
+
condition: 'Account under review',
|
|
20
|
+
conditionKey: 'tier.condition.under_review',
|
|
21
21
|
},
|
|
22
22
|
spore: {
|
|
23
23
|
name: 'Spore',
|
|
24
24
|
emoji: '🍄',
|
|
25
25
|
hourlyPollen: 0.01,
|
|
26
26
|
dailyEstimate: 0.24,
|
|
27
|
-
condition: '
|
|
28
|
-
conditionKey: 'tier.condition.
|
|
27
|
+
condition: 'New account (default)',
|
|
28
|
+
conditionKey: 'tier.condition.default',
|
|
29
29
|
},
|
|
30
30
|
seed: {
|
|
31
31
|
name: 'Seed',
|
|
@@ -40,8 +40,8 @@ export const TIER_INFO = {
|
|
|
40
40
|
emoji: '🌸',
|
|
41
41
|
hourlyPollen: 0.4,
|
|
42
42
|
dailyEstimate: 9.6,
|
|
43
|
-
condition: '
|
|
44
|
-
conditionKey: 'tier.condition.
|
|
43
|
+
condition: 'Complete Quests & contribute',
|
|
44
|
+
conditionKey: 'tier.condition.quests',
|
|
45
45
|
},
|
|
46
46
|
nectar: {
|
|
47
47
|
name: 'Nectar',
|
|
@@ -36,7 +36,8 @@ Use 'action=update' to change these. NEVER confuse Chat Mode with Tools Protecti
|
|
|
36
36
|
costThreshold: tool.schema.number().optional().describe('Cost threshold in USD/🌼 above which confirmation is required'),
|
|
37
37
|
thresholdsTier: tool.schema.number().optional().describe('Warning threshold PERCENTAGE (e.g. 10 for 10%) for Free Tier.'),
|
|
38
38
|
thresholdsWallet: tool.schema.number().optional().describe('Warning threshold PERCENTAGE (e.g. 50 for 50%) for Wallet balance.'),
|
|
39
|
-
lang: tool.schema.enum(['en', 'fr', 'es', 'de', 'it', 'zh']).optional().describe('Plugin language for commands and toasts (en, fr, es, de, it, zh).')
|
|
39
|
+
lang: tool.schema.enum(['en', 'fr', 'es', 'de', 'it', 'zh']).optional().describe('Plugin language for commands and toasts (en, fr, es, de, it, zh).'),
|
|
40
|
+
refillOverride: tool.schema.number().optional().describe('Manual Quest Pollen hourly refill override (0.01, 0.15, 0.4, 0.8, or 10). Set to 0 for auto-deduction.')
|
|
40
41
|
},
|
|
41
42
|
async execute(args, context) {
|
|
42
43
|
if (args.action === 'view') {
|
|
@@ -63,6 +64,9 @@ Use 'action=update' to change these. NEVER confuse Chat Mode with Tools Protecti
|
|
|
63
64
|
updates.enablePaidTools = args.enablePaidTools;
|
|
64
65
|
if (args.costThreshold !== undefined)
|
|
65
66
|
updates.costThreshold = args.costThreshold;
|
|
67
|
+
if (args.refillOverride !== undefined) {
|
|
68
|
+
updates.refillOverride = args.refillOverride === 0 ? undefined : args.refillOverride;
|
|
69
|
+
}
|
|
66
70
|
if (args.lang !== undefined)
|
|
67
71
|
updates.lang = args.lang;
|
|
68
72
|
if (args.thresholdsTier !== undefined || args.thresholdsWallet !== undefined) {
|
package/package.json
CHANGED