opencode-pollinations-plugin 6.1.0-beta.9 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +130 -0
- package/README.es.md +130 -0
- package/README.fr.md +130 -0
- package/README.it.md +130 -0
- package/README.md +87 -73
- package/dist/index.js +52 -161
- package/dist/locales/de.json +374 -0
- package/dist/locales/en.json +373 -0
- package/dist/locales/es.json +374 -0
- package/dist/locales/fr.json +373 -0
- package/dist/locales/index.d.ts +1 -0
- package/dist/locales/index.js +37 -0
- package/dist/locales/it.json +374 -0
- package/dist/server/commands.d.ts +6 -0
- package/dist/server/commands.js +394 -125
- package/dist/server/config.d.ts +34 -23
- package/dist/server/config.js +200 -108
- package/dist/server/connect-response.d.ts +2 -0
- package/dist/server/connect-response.js +59 -0
- package/dist/server/generate-config.d.ts +3 -30
- package/dist/server/generate-config.js +164 -106
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +124 -149
- package/dist/server/logger.d.ts +8 -0
- package/dist/server/logger.js +38 -0
- package/dist/server/models/cache.d.ts +35 -0
- package/dist/server/models/cache.js +160 -0
- package/dist/server/models/fetcher.d.ts +18 -0
- package/dist/server/models/fetcher.js +194 -0
- package/dist/server/models/index.d.ts +6 -0
- package/dist/server/models/index.js +5 -0
- package/dist/server/models/manual.d.ts +15 -0
- package/dist/server/models/manual.js +92 -0
- package/dist/server/models/types.d.ts +55 -0
- package/dist/server/models/types.js +7 -0
- package/dist/server/models/worker.d.ts +22 -0
- package/dist/server/models/worker.js +174 -0
- package/dist/server/pollinations-api.d.ts +11 -0
- package/dist/server/pollinations-api.js +21 -8
- package/dist/server/proxy.js +222 -307
- package/dist/server/quota.d.ts +2 -0
- package/dist/server/quota.js +89 -86
- package/dist/server/scripts/pollinations_pricing.d.ts +8 -0
- package/dist/server/scripts/pollinations_pricing.js +246 -0
- package/dist/server/scripts/test_cost_endpoints.d.ts +1 -0
- package/dist/server/scripts/test_cost_endpoints.js +61 -0
- package/dist/server/scripts/test_dynamic_pricing.d.ts +1 -0
- package/dist/server/scripts/test_dynamic_pricing.js +39 -0
- package/dist/server/scripts/test_freetier_audit.d.ts +11 -0
- package/dist/server/scripts/test_freetier_audit.js +215 -0
- package/dist/server/scripts/test_parallel_cost.d.ts +1 -0
- package/dist/server/scripts/test_parallel_cost.js +104 -0
- package/dist/server/toast.d.ts +7 -1
- package/dist/server/toast.js +43 -10
- package/dist/tools/design/gen_diagram.d.ts +2 -0
- package/dist/tools/design/gen_diagram.js +94 -0
- package/dist/tools/design/gen_palette.d.ts +2 -0
- package/dist/tools/design/gen_palette.js +182 -0
- package/dist/tools/design/gen_qrcode.d.ts +2 -0
- package/dist/tools/design/gen_qrcode.js +50 -0
- package/dist/tools/ffmpeg.d.ts +24 -0
- package/dist/tools/ffmpeg.js +54 -0
- package/dist/tools/index.d.ts +25 -0
- package/dist/tools/index.js +86 -0
- package/dist/tools/pollinations/beta_discovery.d.ts +9 -0
- package/dist/tools/pollinations/beta_discovery.js +201 -0
- package/dist/tools/pollinations/cost-guard.d.ts +38 -0
- package/dist/tools/pollinations/cost-guard.js +136 -0
- package/dist/tools/pollinations/deepsearch.d.ts +7 -0
- package/dist/tools/pollinations/deepsearch.js +80 -0
- package/dist/tools/pollinations/gen_audio.d.ts +18 -0
- package/dist/tools/pollinations/gen_audio.js +220 -0
- package/dist/tools/pollinations/gen_image.d.ts +11 -0
- package/dist/tools/pollinations/gen_image.js +211 -0
- package/dist/tools/pollinations/gen_music.d.ts +14 -0
- package/dist/tools/pollinations/gen_music.js +157 -0
- package/dist/tools/pollinations/gen_video.d.ts +16 -0
- package/dist/tools/pollinations/gen_video.js +249 -0
- package/dist/tools/pollinations/polli_config.d.ts +2 -0
- package/dist/tools/pollinations/polli_config.js +95 -0
- package/dist/tools/pollinations/polli_gen_confirm.d.ts +2 -0
- package/dist/tools/pollinations/polli_gen_confirm.js +48 -0
- package/dist/tools/pollinations/polli_status.d.ts +2 -0
- package/dist/tools/pollinations/polli_status.js +31 -0
- package/dist/tools/pollinations/polli_web_search.d.ts +15 -0
- package/dist/tools/pollinations/polli_web_search.js +126 -0
- package/dist/tools/pollinations/search_crawl_scrape.d.ts +7 -0
- package/dist/tools/pollinations/search_crawl_scrape.js +85 -0
- package/dist/tools/pollinations/shared.d.ts +181 -0
- package/dist/tools/pollinations/shared.js +758 -0
- package/dist/tools/pollinations/test_estimators.d.ts +1 -0
- package/dist/tools/pollinations/test_estimators.js +22 -0
- package/dist/tools/pollinations/transcribe_audio.d.ts +13 -0
- package/dist/tools/pollinations/transcribe_audio.js +171 -0
- package/dist/tools/power/extract_audio.d.ts +2 -0
- package/dist/tools/power/extract_audio.js +179 -0
- package/dist/tools/power/extract_frames.d.ts +2 -0
- package/dist/tools/power/extract_frames.js +237 -0
- package/dist/tools/power/file_to_url.d.ts +2 -0
- package/dist/tools/power/file_to_url.js +217 -0
- package/dist/tools/power/remove_background.d.ts +2 -0
- package/dist/tools/power/remove_background.js +404 -0
- package/dist/tools/power/rmbg_keys.d.ts +2 -0
- package/dist/tools/power/rmbg_keys.js +79 -0
- package/dist/tools/shared.d.ts +30 -0
- package/dist/tools/shared.js +80 -0
- package/package.json +10 -4
- package/dist/server/models-seed.d.ts +0 -18
- package/dist/server/models-seed.js +0 -55
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { resolveOutputDir, TOOL_DIRS } from '../shared.js';
|
|
5
|
+
function hexToHSL(hex) {
|
|
6
|
+
hex = hex.replace('#', '');
|
|
7
|
+
if (hex.length === 3)
|
|
8
|
+
hex = hex.split('').map(c => c + c).join('');
|
|
9
|
+
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
|
10
|
+
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
|
11
|
+
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
|
12
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
13
|
+
let h = 0, s = 0;
|
|
14
|
+
const l = (max + min) / 2;
|
|
15
|
+
if (max !== min) {
|
|
16
|
+
const d = max - min;
|
|
17
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
18
|
+
switch (max) {
|
|
19
|
+
case r:
|
|
20
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
21
|
+
break;
|
|
22
|
+
case g:
|
|
23
|
+
h = ((b - r) / d + 2) / 6;
|
|
24
|
+
break;
|
|
25
|
+
case b:
|
|
26
|
+
h = ((r - g) / d + 4) / 6;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
|
|
31
|
+
}
|
|
32
|
+
function hslToHex(h, s, l) {
|
|
33
|
+
s /= 100;
|
|
34
|
+
l /= 100;
|
|
35
|
+
const a = s * Math.min(l, 1 - l);
|
|
36
|
+
const f = (n) => {
|
|
37
|
+
const k = (n + h / 30) % 12;
|
|
38
|
+
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
39
|
+
return Math.round(255 * color).toString(16).padStart(2, '0');
|
|
40
|
+
};
|
|
41
|
+
return `#${f(0)}${f(8)}${f(4)}`;
|
|
42
|
+
}
|
|
43
|
+
function generatePalette(baseHex, scheme, count) {
|
|
44
|
+
const base = hexToHSL(baseHex);
|
|
45
|
+
const colors = [];
|
|
46
|
+
switch (scheme) {
|
|
47
|
+
case 'complementary':
|
|
48
|
+
colors.push({ hex: baseHex, role: 'Base' });
|
|
49
|
+
colors.push({ hex: hslToHex((base.h + 180) % 360, base.s, base.l), role: 'Complement' });
|
|
50
|
+
// Fill shades
|
|
51
|
+
for (let i = 2; i < count; i++) {
|
|
52
|
+
const lShift = base.l + (i % 2 === 0 ? 15 : -15) * Math.ceil(i / 2);
|
|
53
|
+
colors.push({ hex: hslToHex(base.h, base.s, Math.max(10, Math.min(90, lShift))), role: `Shade ${i - 1}` });
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
case 'analogous':
|
|
57
|
+
for (let i = 0; i < count; i++) {
|
|
58
|
+
const offset = (i - Math.floor(count / 2)) * 30;
|
|
59
|
+
colors.push({
|
|
60
|
+
hex: hslToHex((base.h + offset + 360) % 360, base.s, base.l),
|
|
61
|
+
role: offset === 0 ? 'Base' : `${offset > 0 ? '+' : ''}${offset}°`
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
case 'triadic':
|
|
66
|
+
colors.push({ hex: baseHex, role: 'Base' });
|
|
67
|
+
colors.push({ hex: hslToHex((base.h + 120) % 360, base.s, base.l), role: 'Triad +120°' });
|
|
68
|
+
colors.push({ hex: hslToHex((base.h + 240) % 360, base.s, base.l), role: 'Triad +240°' });
|
|
69
|
+
for (let i = 3; i < count; i++) {
|
|
70
|
+
const lShift = base.l + (i % 2 === 0 ? 12 : -12) * Math.ceil((i - 2) / 2);
|
|
71
|
+
colors.push({ hex: hslToHex((base.h + (i * 120)) % 360, base.s, Math.max(10, Math.min(90, lShift))), role: `Accent ${i - 2}` });
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
case 'split-complementary':
|
|
75
|
+
colors.push({ hex: baseHex, role: 'Base' });
|
|
76
|
+
colors.push({ hex: hslToHex((base.h + 150) % 360, base.s, base.l), role: 'Split +150°' });
|
|
77
|
+
colors.push({ hex: hslToHex((base.h + 210) % 360, base.s, base.l), role: 'Split +210°' });
|
|
78
|
+
for (let i = 3; i < count; i++) {
|
|
79
|
+
colors.push({ hex: hslToHex(base.h, base.s, Math.max(10, Math.min(90, base.l + (i * 10 - 30)))), role: `Tone ${i - 2}` });
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
case 'monochromatic':
|
|
83
|
+
default:
|
|
84
|
+
for (let i = 0; i < count; i++) {
|
|
85
|
+
const l = Math.round(15 + (i / (count - 1)) * 70); // 15% to 85%
|
|
86
|
+
colors.push({
|
|
87
|
+
hex: hslToHex(base.h, base.s, l),
|
|
88
|
+
role: l < base.l ? `Dark ${Math.abs(i - Math.floor(count / 2))}` : l === base.l ? 'Base' : `Light ${Math.abs(i - Math.floor(count / 2))}`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// Mark closest to base
|
|
92
|
+
let closestIdx = 0;
|
|
93
|
+
let closestDiff = Infinity;
|
|
94
|
+
colors.forEach((c, i) => {
|
|
95
|
+
const diff = Math.abs(hexToHSL(c.hex).l - base.l);
|
|
96
|
+
if (diff < closestDiff) {
|
|
97
|
+
closestDiff = diff;
|
|
98
|
+
closestIdx = i;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
colors[closestIdx].role = 'Base';
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
return colors.slice(0, count);
|
|
105
|
+
}
|
|
106
|
+
function generateSVG(colors) {
|
|
107
|
+
const swatchW = 120;
|
|
108
|
+
const swatchH = 80;
|
|
109
|
+
const gap = 8;
|
|
110
|
+
const totalW = colors.length * (swatchW + gap) - gap + 40;
|
|
111
|
+
const totalH = swatchH + 60;
|
|
112
|
+
const swatches = colors.map((c, i) => {
|
|
113
|
+
const x = 20 + i * (swatchW + gap);
|
|
114
|
+
const textColor = hexToHSL(c.hex).l > 50 ? '#1a1a1a' : '#ffffff';
|
|
115
|
+
return `
|
|
116
|
+
<rect x="${x}" y="20" width="${swatchW}" height="${swatchH}" rx="8" fill="${c.hex}" stroke="#333" stroke-width="1"/>
|
|
117
|
+
<text x="${x + swatchW / 2}" y="${swatchH / 2 + 15}" text-anchor="middle" fill="${textColor}" font-family="monospace" font-size="13" font-weight="bold">${c.hex.toUpperCase()}</text>
|
|
118
|
+
<text x="${x + swatchW / 2}" y="${swatchH + 38}" text-anchor="middle" fill="#666" font-family="sans-serif" font-size="11">${c.role}</text>`;
|
|
119
|
+
}).join('');
|
|
120
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${totalW}" height="${totalH}" viewBox="0 0 ${totalW} ${totalH}">
|
|
121
|
+
<rect width="100%" height="100%" fill="#0d0d0d" rx="12"/>
|
|
122
|
+
${swatches}
|
|
123
|
+
</svg>`;
|
|
124
|
+
}
|
|
125
|
+
export const genPaletteTool = tool({
|
|
126
|
+
description: `Generate a harmonious color palette from a base hex color.
|
|
127
|
+
Outputs a visual SVG palette + JSON color codes. Works 100% offline.
|
|
128
|
+
Schemes: monochromatic, complementary, analogous, triadic, split-complementary.
|
|
129
|
+
Perfect for frontend design, branding, and UI theming.`,
|
|
130
|
+
args: {
|
|
131
|
+
color: tool.schema.string().describe('Base hex color (e.g. "#3B82F6" or "3B82F6")'),
|
|
132
|
+
scheme: tool.schema.enum(['monochromatic', 'complementary', 'analogous', 'triadic', 'split-complementary']).optional()
|
|
133
|
+
.describe('Color harmony scheme (default: analogous)'),
|
|
134
|
+
count: tool.schema.number().min(3).max(8).optional().describe('Number of colors (default: 5, max: 8)'),
|
|
135
|
+
filename: tool.schema.string().optional().describe('Custom filename (without extension). Auto-generated if omitted'),
|
|
136
|
+
output_path: tool.schema.string().optional().describe('Custom output directory. Default: ~/Downloads/pollinations/palettes/'),
|
|
137
|
+
},
|
|
138
|
+
async execute(args, context) {
|
|
139
|
+
const scheme = args.scheme || 'analogous';
|
|
140
|
+
const count = args.count || 5;
|
|
141
|
+
// Normalize hex
|
|
142
|
+
let hex = args.color.trim();
|
|
143
|
+
if (!hex.startsWith('#'))
|
|
144
|
+
hex = '#' + hex;
|
|
145
|
+
if (!/^#[0-9a-fA-F]{3,6}$/.test(hex)) {
|
|
146
|
+
return `❌ Invalid hex color: "${args.color}". Use format: #3B82F6 or 3B82F6`;
|
|
147
|
+
}
|
|
148
|
+
if (hex.length === 4)
|
|
149
|
+
hex = '#' + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];
|
|
150
|
+
// Generate palette
|
|
151
|
+
const colors = generatePalette(hex, scheme, count);
|
|
152
|
+
const outputDir = resolveOutputDir(TOOL_DIRS.palettes, args.output_path);
|
|
153
|
+
// Save SVG
|
|
154
|
+
const safeName = args.filename
|
|
155
|
+
? args.filename.replace(/[^a-zA-Z0-9_-]/g, '_')
|
|
156
|
+
: `palette_${hex.replace('#', '')}_${scheme}`;
|
|
157
|
+
const svgPath = path.join(outputDir, `${safeName}.svg`);
|
|
158
|
+
const svg = generateSVG(colors);
|
|
159
|
+
fs.writeFileSync(svgPath, svg);
|
|
160
|
+
// Build CSS custom properties snippet
|
|
161
|
+
const cssVars = colors.map((c, i) => ` --color-${i + 1}: ${c.hex};`).join('\n');
|
|
162
|
+
context.metadata({ title: `🎨 Palette: ${scheme} from ${hex}` });
|
|
163
|
+
const colorTable = colors.map(c => ` ${c.hex.toUpperCase()} ${c.role}`).join('\n');
|
|
164
|
+
return [
|
|
165
|
+
`🎨 Color Palette Generated`,
|
|
166
|
+
`━━━━━━━━━━━━━━━━━━━━━━━━━`,
|
|
167
|
+
`Base: ${hex.toUpperCase()}`,
|
|
168
|
+
`Scheme: ${scheme}`,
|
|
169
|
+
`Colors (${count}):`,
|
|
170
|
+
colorTable,
|
|
171
|
+
``,
|
|
172
|
+
`File: ${svgPath}`,
|
|
173
|
+
``,
|
|
174
|
+
`CSS Variables:`,
|
|
175
|
+
`:root {`,
|
|
176
|
+
cssVars,
|
|
177
|
+
`}`,
|
|
178
|
+
``,
|
|
179
|
+
`Cost: Free (local computation)`,
|
|
180
|
+
].join('\n');
|
|
181
|
+
},
|
|
182
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
+
import * as QRCode from 'qrcode';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { resolveOutputDir, TOOL_DIRS } from '../shared.js';
|
|
6
|
+
export const genQrcodeTool = tool({
|
|
7
|
+
description: `Generate a QR code image from text, URL, or WiFi credentials.
|
|
8
|
+
Outputs a PNG file saved locally. Works 100% offline, no API key needed.
|
|
9
|
+
Examples: URLs, plain text, WiFi (format: WIFI:T:WPA;S:NetworkName;P:Password;;)`,
|
|
10
|
+
args: {
|
|
11
|
+
content: tool.schema.string().describe('The text, URL, or WiFi string to encode into a QR code'),
|
|
12
|
+
size: tool.schema.number().min(128).max(2048).optional().describe('QR code size in pixels (default: 512)'),
|
|
13
|
+
filename: tool.schema.string().optional().describe('Custom filename (without extension). Auto-generated if omitted'),
|
|
14
|
+
output_path: tool.schema.string().optional().describe('Custom output directory. Default: ~/Downloads/pollinations/qrcodes/'),
|
|
15
|
+
},
|
|
16
|
+
async execute(args, context) {
|
|
17
|
+
const size = args.size || 512;
|
|
18
|
+
const outputDir = resolveOutputDir(TOOL_DIRS.qrcodes, args.output_path);
|
|
19
|
+
const safeName = args.filename
|
|
20
|
+
? args.filename.replace(/[^a-zA-Z0-9_-]/g, '_')
|
|
21
|
+
: `qr_${Date.now()}`;
|
|
22
|
+
const filePath = path.join(outputDir, `${safeName}.png`);
|
|
23
|
+
try {
|
|
24
|
+
await QRCode.toFile(filePath, args.content, {
|
|
25
|
+
width: size,
|
|
26
|
+
margin: 2,
|
|
27
|
+
color: { dark: '#000000', light: '#ffffff' },
|
|
28
|
+
errorCorrectionLevel: 'M',
|
|
29
|
+
});
|
|
30
|
+
const stats = fs.statSync(filePath);
|
|
31
|
+
const fileSizeKB = (stats.size / 1024).toFixed(1);
|
|
32
|
+
const displayContent = args.content.length > 80
|
|
33
|
+
? args.content.substring(0, 77) + '...'
|
|
34
|
+
: args.content;
|
|
35
|
+
context.metadata({ title: `🔲 QR Code: ${displayContent}` });
|
|
36
|
+
return [
|
|
37
|
+
`🔲 QR Code Généré`,
|
|
38
|
+
`━━━━━━━━━━━━━━━━━━`,
|
|
39
|
+
`Contenu: ${displayContent}`,
|
|
40
|
+
`Taille: ${size}×${size}px`,
|
|
41
|
+
`Fichier: ${filePath}`,
|
|
42
|
+
`Poids: ${fileSizeKB} KB`,
|
|
43
|
+
`Coût: Gratuit (génération locale)`,
|
|
44
|
+
].join('\n');
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
return `❌ Erreur QR Code: ${err.message}`;
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if ffmpeg is available in the system PATH
|
|
3
|
+
*/
|
|
4
|
+
export declare function hasSystemFFmpeg(): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Check if ffprobe is available in the system PATH
|
|
7
|
+
*/
|
|
8
|
+
export declare function hasSystemFFprobe(): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Get cross-platform installation instructions
|
|
11
|
+
*/
|
|
12
|
+
export declare function getFFmpegInstallInstructions(): string;
|
|
13
|
+
/**
|
|
14
|
+
* Helper to run ffmpeg commands safely
|
|
15
|
+
*/
|
|
16
|
+
export declare function runFFmpeg(args: string[], options?: {
|
|
17
|
+
timeout?: number;
|
|
18
|
+
}): void;
|
|
19
|
+
/**
|
|
20
|
+
* Helper to run ffprobe commands safely and return stdout
|
|
21
|
+
*/
|
|
22
|
+
export declare function runFFprobe(args: string[], options?: {
|
|
23
|
+
timeout?: number;
|
|
24
|
+
}): string;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { spawnSync } from 'child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Check if ffmpeg is available in the system PATH
|
|
4
|
+
*/
|
|
5
|
+
export function hasSystemFFmpeg() {
|
|
6
|
+
const result = spawnSync('ffmpeg', ['-version'], { stdio: 'ignore' });
|
|
7
|
+
return result.status === 0 && !result.error;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Check if ffprobe is available in the system PATH
|
|
11
|
+
*/
|
|
12
|
+
export function hasSystemFFprobe() {
|
|
13
|
+
const result = spawnSync('ffprobe', ['-version'], { stdio: 'ignore' });
|
|
14
|
+
return result.status === 0 && !result.error;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Get cross-platform installation instructions
|
|
18
|
+
*/
|
|
19
|
+
export function getFFmpegInstallInstructions() {
|
|
20
|
+
const platform = process.platform;
|
|
21
|
+
const instructions = {
|
|
22
|
+
linux: 'sudo apt install ffmpeg (Debian/Ubuntu)\nsudo dnf install ffmpeg (Fedora)',
|
|
23
|
+
darwin: 'brew install ffmpeg',
|
|
24
|
+
win32: 'choco install ffmpeg (Chocolatey)\nwinget install ffmpeg (WinGet)\nOu télécharger sur https://ffmpeg.org/download.html',
|
|
25
|
+
};
|
|
26
|
+
return instructions[platform] || 'Voir https://ffmpeg.org/download.html';
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Helper to run ffmpeg commands safely
|
|
30
|
+
*/
|
|
31
|
+
export function runFFmpeg(args, options = {}) {
|
|
32
|
+
const result = spawnSync('ffmpeg', args, {
|
|
33
|
+
stdio: 'ignore',
|
|
34
|
+
timeout: options.timeout || 120000,
|
|
35
|
+
});
|
|
36
|
+
if (result.error)
|
|
37
|
+
throw result.error;
|
|
38
|
+
if (result.status !== 0)
|
|
39
|
+
throw new Error(`FFmpeg failed with code ${result.status}`);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Helper to run ffprobe commands safely and return stdout
|
|
43
|
+
*/
|
|
44
|
+
export function runFFprobe(args, options = {}) {
|
|
45
|
+
const result = spawnSync('ffprobe', args, {
|
|
46
|
+
encoding: 'utf-8',
|
|
47
|
+
timeout: options.timeout || 15000,
|
|
48
|
+
});
|
|
49
|
+
if (result.error)
|
|
50
|
+
throw result.error;
|
|
51
|
+
if (result.status !== 0)
|
|
52
|
+
throw new Error(`FFprobe failed: ${result.stderr}`);
|
|
53
|
+
return result.stdout;
|
|
54
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Registry — Conditional Injection System
|
|
3
|
+
*
|
|
4
|
+
* Free Universe (no key): 8 tools always available
|
|
5
|
+
* Enter Universe (with key): +6 Pollinations tools
|
|
6
|
+
*
|
|
7
|
+
* Tools are injected ONCE at plugin init. Restart needed after /poll connect.
|
|
8
|
+
*/
|
|
9
|
+
import { polliGenImageTool } from './pollinations/gen_image.js';
|
|
10
|
+
import { polliGenVideoTool } from './pollinations/gen_video.js';
|
|
11
|
+
import { polliGenAudioTool } from './pollinations/gen_audio.js';
|
|
12
|
+
import { polliSttTool } from './pollinations/transcribe_audio.js';
|
|
13
|
+
import { polliGenMusicTool } from './pollinations/gen_music.js';
|
|
14
|
+
import { polliWebSearchTool } from './pollinations/polli_web_search.js';
|
|
15
|
+
import { polliBetaDiscoveryTool } from './pollinations/beta_discovery.js';
|
|
16
|
+
import { polliGenConfirmTool } from './pollinations/polli_gen_confirm.js';
|
|
17
|
+
import { polliStatusTool } from './pollinations/polli_status.js';
|
|
18
|
+
import { polliConfigTool } from './pollinations/polli_config.js';
|
|
19
|
+
/**
|
|
20
|
+
* Build the tool registry based on user's access level
|
|
21
|
+
*
|
|
22
|
+
* @returns Record<string, Tool> to be spread into the plugin's tool: {} property
|
|
23
|
+
*/
|
|
24
|
+
export declare function createToolRegistry(): Record<string, any>;
|
|
25
|
+
export { polliGenImageTool, polliGenVideoTool, polliGenAudioTool, polliSttTool, polliGenMusicTool, polliWebSearchTool, polliBetaDiscoveryTool, polliGenConfirmTool, polliStatusTool, polliConfigTool };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Registry — Conditional Injection System
|
|
3
|
+
*
|
|
4
|
+
* Free Universe (no key): 8 tools always available
|
|
5
|
+
* Enter Universe (with key): +6 Pollinations tools
|
|
6
|
+
*
|
|
7
|
+
* Tools are injected ONCE at plugin init. Restart needed after /poll connect.
|
|
8
|
+
*/
|
|
9
|
+
import { loadConfig } from '../server/config.js';
|
|
10
|
+
// === FREE TOOLS (Always available) ===
|
|
11
|
+
import { genQrcodeTool } from './design/gen_qrcode.js';
|
|
12
|
+
import { genDiagramTool } from './design/gen_diagram.js';
|
|
13
|
+
import { genPaletteTool } from './design/gen_palette.js';
|
|
14
|
+
import { fileToUrlTool } from './power/file_to_url.js';
|
|
15
|
+
import { removeBackgroundTool } from './power/remove_background.js';
|
|
16
|
+
import { extractFramesTool } from './power/extract_frames.js';
|
|
17
|
+
import { extractAudioTool } from './power/extract_audio.js';
|
|
18
|
+
import { rmbgKeysTool } from './power/rmbg_keys.js';
|
|
19
|
+
// === ENTER TOOLS (Require API key) ===
|
|
20
|
+
import { polliGenImageTool } from './pollinations/gen_image.js';
|
|
21
|
+
import { polliGenVideoTool } from './pollinations/gen_video.js';
|
|
22
|
+
import { polliGenAudioTool } from './pollinations/gen_audio.js';
|
|
23
|
+
import { polliSttTool } from './pollinations/transcribe_audio.js';
|
|
24
|
+
import { polliGenMusicTool } from './pollinations/gen_music.js';
|
|
25
|
+
import { polliWebSearchTool } from './pollinations/polli_web_search.js';
|
|
26
|
+
import { polliBetaDiscoveryTool } from './pollinations/beta_discovery.js';
|
|
27
|
+
import { polliGenConfirmTool } from './pollinations/polli_gen_confirm.js';
|
|
28
|
+
import { polliStatusTool } from './pollinations/polli_status.js';
|
|
29
|
+
import { polliConfigTool } from './pollinations/polli_config.js';
|
|
30
|
+
import { log } from '../server/logger.js';
|
|
31
|
+
/**
|
|
32
|
+
* Detect if a valid API key is present
|
|
33
|
+
*/
|
|
34
|
+
function hasValidKey() {
|
|
35
|
+
const config = loadConfig();
|
|
36
|
+
return !!(config.apiKey && config.apiKey.length > 5 && config.apiKey !== 'dummy');
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build the tool registry based on user's access level
|
|
40
|
+
*
|
|
41
|
+
* @returns Record<string, Tool> to be spread into the plugin's tool: {} property
|
|
42
|
+
*/
|
|
43
|
+
export function createToolRegistry() {
|
|
44
|
+
const tools = {};
|
|
45
|
+
const keyPresent = hasValidKey();
|
|
46
|
+
// === FREE UNIVERSE: Always injected (8 tools) ===
|
|
47
|
+
// Design tools (3)
|
|
48
|
+
tools['gen_qrcode'] = genQrcodeTool;
|
|
49
|
+
tools['gen_diagram'] = genDiagramTool;
|
|
50
|
+
tools['gen_palette'] = genPaletteTool;
|
|
51
|
+
// Power tools (5)
|
|
52
|
+
tools['file_to_url'] = fileToUrlTool;
|
|
53
|
+
tools['remove_background'] = removeBackgroundTool;
|
|
54
|
+
tools['extract_frames'] = extractFramesTool;
|
|
55
|
+
tools['extract_audio'] = extractAudioTool;
|
|
56
|
+
tools['rmbg_keys'] = rmbgKeysTool;
|
|
57
|
+
log(`Free tools injected: ${Object.keys(tools).length}`);
|
|
58
|
+
// === ENTER UNIVERSE: Only with valid API key (+6 tools) ===
|
|
59
|
+
if (keyPresent) {
|
|
60
|
+
// Pollinations media tools
|
|
61
|
+
tools['polli_gen_image'] = polliGenImageTool;
|
|
62
|
+
tools['polli_gen_video'] = polliGenVideoTool;
|
|
63
|
+
tools['polli_gen_audio'] = polliGenAudioTool;
|
|
64
|
+
tools['polli_stt'] = polliSttTool;
|
|
65
|
+
tools['polli_gen_music'] = polliGenMusicTool;
|
|
66
|
+
// Unified search tool
|
|
67
|
+
tools['polli_web_search'] = polliWebSearchTool;
|
|
68
|
+
// Cost Guard Confirmation tool
|
|
69
|
+
tools['polli_gen_confirm'] = polliGenConfirmTool;
|
|
70
|
+
// Model API discovery & diagnostics
|
|
71
|
+
tools['polli_beta_discovery'] = polliBetaDiscoveryTool;
|
|
72
|
+
// Plugin Configuration editor (Agents)
|
|
73
|
+
tools['polli_config'] = polliConfigTool;
|
|
74
|
+
// Plugin Status / Info / Pricing helper map
|
|
75
|
+
tools['polli_status'] = polliStatusTool;
|
|
76
|
+
log(`Enter tools injected (key detected). Total: ${Object.keys(tools).length}`);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// En mode gratuit, on ajoute quand meme polli_status mais restraint (il dira manque une clé pour full profile etc)
|
|
80
|
+
tools['polli_status'] = polliStatusTool;
|
|
81
|
+
log(`Enter tools SKIPPED (no key). Total: ${Object.keys(tools).length}`);
|
|
82
|
+
}
|
|
83
|
+
return tools;
|
|
84
|
+
}
|
|
85
|
+
// Re-export for convenience
|
|
86
|
+
export { polliGenImageTool, polliGenVideoTool, polliGenAudioTool, polliSttTool, polliGenMusicTool, polliWebSearchTool, polliBetaDiscoveryTool, polliGenConfirmTool, polliStatusTool, polliConfigTool };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* beta_discovery Tool (API Explorer V3 - Hybrid Probe)
|
|
3
|
+
*
|
|
4
|
+
* Combines reading the official OpenAPI Specification with active
|
|
5
|
+
* blackbox probing (triggering HTTP 400/422 ValidationErrors) to
|
|
6
|
+
* discover hidden or undocumented enums and parameters.
|
|
7
|
+
*/
|
|
8
|
+
import { type ToolDefinition } from '@opencode-ai/plugin/tool';
|
|
9
|
+
export declare const polliBetaDiscoveryTool: ToolDefinition;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* beta_discovery Tool (API Explorer V3 - Hybrid Probe)
|
|
3
|
+
*
|
|
4
|
+
* Combines reading the official OpenAPI Specification with active
|
|
5
|
+
* blackbox probing (triggering HTTP 400/422 ValidationErrors) to
|
|
6
|
+
* discover hidden or undocumented enums and parameters.
|
|
7
|
+
*/
|
|
8
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
9
|
+
import { emitStatusToast } from '../../server/toast.js';
|
|
10
|
+
import { getApiKey } from './shared.js';
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import * as https from 'https';
|
|
13
|
+
// Primary URL for the OpenAPI spec
|
|
14
|
+
const OPENAPI_URL = 'https://enter.pollinations.ai/api/docs/open-api/generate-schema';
|
|
15
|
+
// Fallback local path
|
|
16
|
+
const LOCAL_FALLBACK_PATH = '/home/fkomp/Bureau/oracle/Documentations/API - Severals documentations for multiples api usages/pollinations/pollinations_enter_beta/PolinationsGenBeta_api.json';
|
|
17
|
+
let cachedSchema = null;
|
|
18
|
+
async function fetchOpenApiSchema() {
|
|
19
|
+
if (cachedSchema)
|
|
20
|
+
return cachedSchema;
|
|
21
|
+
try {
|
|
22
|
+
if (fs.existsSync(LOCAL_FALLBACK_PATH)) {
|
|
23
|
+
const data = fs.readFileSync(LOCAL_FALLBACK_PATH, 'utf-8');
|
|
24
|
+
cachedSchema = JSON.parse(data);
|
|
25
|
+
return cachedSchema;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
// Fallthrough
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const response = await fetch(OPENAPI_URL);
|
|
33
|
+
if (!response.ok)
|
|
34
|
+
throw new Error(`HTTP ${response.status}`);
|
|
35
|
+
cachedSchema = await response.json();
|
|
36
|
+
return cachedSchema;
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
throw new Error(`Failed to load OpenAPI Schema: ${e.message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function probeEndpoint(method, endpointUrl, payloadStr) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const apiKey = getApiKey();
|
|
45
|
+
const urlObj = new URL(endpointUrl.startsWith('http') ? endpointUrl : `https://gen.pollinations.ai${endpointUrl.startsWith('/') ? '' : '/'}${endpointUrl}`);
|
|
46
|
+
const options = {
|
|
47
|
+
method: method,
|
|
48
|
+
headers: {
|
|
49
|
+
'User-Agent': 'OpenCode-Probe-Tool/3.0',
|
|
50
|
+
'Accept': 'application/json'
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
if (apiKey) {
|
|
54
|
+
options.headers['Authorization'] = `Bearer ${apiKey}`;
|
|
55
|
+
}
|
|
56
|
+
let postData;
|
|
57
|
+
if (method === 'POST') {
|
|
58
|
+
options.headers['Content-Type'] = 'application/json';
|
|
59
|
+
if (payloadStr) {
|
|
60
|
+
try {
|
|
61
|
+
// Try to parse just to validate it's json, but send the string
|
|
62
|
+
JSON.parse(payloadStr);
|
|
63
|
+
postData = payloadStr;
|
|
64
|
+
options.headers['Content-Length'] = Buffer.byteLength(postData);
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
return resolve(`❌ Error: payload_json must be a valid JSON string. Parse error: ${e.message}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
postData = '{}';
|
|
72
|
+
options.headers['Content-Length'] = Buffer.byteLength(postData);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else if (method === 'GET' && payloadStr) {
|
|
76
|
+
try {
|
|
77
|
+
const queryParams = JSON.parse(payloadStr);
|
|
78
|
+
for (const [key, value] of Object.entries(queryParams)) {
|
|
79
|
+
urlObj.searchParams.append(key, String(value));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
return resolve(`❌ Error: payload_json must be a valid JSON string representing query params. Parse error: ${e.message}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const req = https.request(urlObj, options, (res) => {
|
|
87
|
+
let data = '';
|
|
88
|
+
res.on('data', chunk => data += chunk);
|
|
89
|
+
res.on('end', () => {
|
|
90
|
+
let formattedResult = `**HTTP Status:** \`${res.statusCode} ${res.statusMessage}\`\n`;
|
|
91
|
+
formattedResult += `**Content-Type:** \`${res.headers['content-type']}\`\n\n`;
|
|
92
|
+
try {
|
|
93
|
+
const parsed = JSON.parse(data);
|
|
94
|
+
// Highlight Validation Errors (The main goal of the probe)
|
|
95
|
+
if (res.statusCode === 400 || res.statusCode === 422 || parsed.fieldErrors || parsed.error) {
|
|
96
|
+
formattedResult += `### 🚨 Validation Error Detected (Jackpot!)\n`;
|
|
97
|
+
formattedResult += `\`\`\`json\n${JSON.stringify(parsed, null, 2)}\n\`\`\``;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
formattedResult += `### Response Body\n`;
|
|
101
|
+
formattedResult += `\`\`\`json\n${JSON.stringify(parsed, null, 2).substring(0, 2000)}${data.length > 2000 ? '\n... (truncated)' : ''}\n\`\`\``;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
// Not JSON
|
|
106
|
+
formattedResult += `### Raw Response Body\n`;
|
|
107
|
+
formattedResult += `\`\`\`text\n${data.substring(0, 2000)}${data.length > 2000 ? '\n... (truncated)' : ''}\n\`\`\``;
|
|
108
|
+
}
|
|
109
|
+
resolve(formattedResult);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
req.on('error', (e) => resolve(`❌ Request Error: ${e.message}`));
|
|
113
|
+
if (postData)
|
|
114
|
+
req.write(postData);
|
|
115
|
+
req.end();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
export const polliBetaDiscoveryTool = tool({
|
|
119
|
+
description: `Explore the Pollinations API using Hybrid Discovery (OpenAPI + Active Probing).
|
|
120
|
+
Use this tool ONLY to reverse engineer and fill the manual registry. Do not use this tool lightly for general operations.
|
|
121
|
+
|
|
122
|
+
🔥 CRITICAL RULES FOR AI AGENTS:
|
|
123
|
+
1. The EXACT and ONLY base URL for any generative media endpoints (image, video, audio) is: **https://gen.pollinations.ai**
|
|
124
|
+
2. The URL 'enter.pollinations.ai' is strictly reserved for the OpenAPI schema documentation and account management. Do NOT hallucinate target endpoints on it!
|
|
125
|
+
|
|
126
|
+
Commands available:
|
|
127
|
+
- 'list_endpoints': (Whitebox) Returns all routes from OpenAPI.
|
|
128
|
+
- 'get_endpoint': (Whitebox) Returns param schema for a route from OpenAPI.
|
|
129
|
+
- 'get_enums': (Whitebox) Recursively searches the OpenAPI spec for a parameter enum (e.g. 'voice', 'model').
|
|
130
|
+
- 'probe_endpoint': (Blackbox) Sends a real HTTP request (GET or POST) to trigger API validation errors (HTTP 400). Use this to reverse-engineer undocumented enums by sending invalid values. The tool auto-injects your API key.`,
|
|
131
|
+
args: {
|
|
132
|
+
command: tool.schema.enum(['list_endpoints', 'get_endpoint', 'get_enums', 'probe_endpoint']).describe('The action to perform'),
|
|
133
|
+
endpoint_path: tool.schema.string().optional().describe('OpenAPI path (e.g. "/v1/chat/completions") or full URL for probe_endpoint'),
|
|
134
|
+
parameter_name: tool.schema.string().optional().describe('Required for get_enums (e.g. "voice", "model")'),
|
|
135
|
+
probe_method: tool.schema.enum(['GET', 'POST']).optional().describe('Required for probe_endpoint'),
|
|
136
|
+
probe_payload_json: tool.schema.string().optional().describe('JSON string of query params (GET) or body (POST) to send during probe_endpoint. e.g. "{\\"model\\":\\"fake-model\\"}" to trigger an error showing valid models.'),
|
|
137
|
+
},
|
|
138
|
+
async execute(args, context) {
|
|
139
|
+
emitStatusToast('info', `Discovery: ${args.command}...`, '🔎 API Probe');
|
|
140
|
+
context.metadata({ title: `Probe: ${args.command}` });
|
|
141
|
+
try {
|
|
142
|
+
if (args.command === 'probe_endpoint') {
|
|
143
|
+
if (!args.endpoint_path || !args.probe_method) {
|
|
144
|
+
return '❌ Error: `endpoint_path` and `probe_method` are required for command `probe_endpoint`';
|
|
145
|
+
}
|
|
146
|
+
return await probeEndpoint(args.probe_method, args.endpoint_path, args.probe_payload_json);
|
|
147
|
+
}
|
|
148
|
+
// Whitebox commands need OpenAPI
|
|
149
|
+
const schema = await fetchOpenApiSchema();
|
|
150
|
+
if (args.command === 'list_endpoints') {
|
|
151
|
+
const paths = Object.keys(schema.paths || {});
|
|
152
|
+
return `**Available API Endpoints (OpenAPI):**\n\n\`\`\`json\n${JSON.stringify(paths, null, 2)}\n\`\`\``;
|
|
153
|
+
}
|
|
154
|
+
if (args.command === 'get_endpoint') {
|
|
155
|
+
if (!args.endpoint_path)
|
|
156
|
+
return '❌ Error: `endpoint_path` is required';
|
|
157
|
+
const details = schema.paths[args.endpoint_path];
|
|
158
|
+
if (!details)
|
|
159
|
+
return `❌ Endpoint '${args.endpoint_path}' not found in OpenAPI schema.`;
|
|
160
|
+
return `**Endpoint Details for \`${args.endpoint_path}\`:**\n\n\`\`\`json\n${JSON.stringify(details, null, 2)}\n\`\`\``;
|
|
161
|
+
}
|
|
162
|
+
if (args.command === 'get_enums') {
|
|
163
|
+
if (!args.parameter_name)
|
|
164
|
+
return '❌ Error: `parameter_name` is required';
|
|
165
|
+
const results = [];
|
|
166
|
+
const findEnums = (obj, path = '') => {
|
|
167
|
+
if (typeof obj !== 'object' || obj === null)
|
|
168
|
+
return;
|
|
169
|
+
if (obj.enum && Array.isArray(obj.enum) && path.includes(args.parameter_name)) {
|
|
170
|
+
results.push({
|
|
171
|
+
path: path,
|
|
172
|
+
enum: obj.enum,
|
|
173
|
+
description: obj.description
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
177
|
+
findEnums(value, path ? `${path}.${key}` : key);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
findEnums(schema.components?.schemas, 'components.schemas');
|
|
181
|
+
findEnums(schema.paths, 'paths');
|
|
182
|
+
if (results.length === 0) {
|
|
183
|
+
return `ℹ️ No enums found matching parameter '${args.parameter_name}' in OpenAPI. You should use 'probe_endpoint' to test it manually!`;
|
|
184
|
+
}
|
|
185
|
+
let output = `**Discovered Enums for '${args.parameter_name}':**\n\n`;
|
|
186
|
+
results.forEach(res => {
|
|
187
|
+
output += `- **Found at**: \`${res.path}\`\n`;
|
|
188
|
+
if (res.description)
|
|
189
|
+
output += ` - **Desc**: ${res.description}\n`;
|
|
190
|
+
output += ` - **Values**: ${res.enum.join(', ')}\n\n`;
|
|
191
|
+
});
|
|
192
|
+
return output;
|
|
193
|
+
}
|
|
194
|
+
return '❌ Unknown command';
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
emitStatusToast('error', `Discovery failed: ${err.message}`, '🔎 API Probe');
|
|
198
|
+
return `❌ Critical Error: ${err.message}`;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
});
|