opencode-pollinations-plugin 6.0.0 → 6.1.0-beta.10
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.md +140 -87
- package/dist/index.js +33 -154
- package/dist/server/commands.d.ts +2 -0
- package/dist/server/commands.js +84 -25
- package/dist/server/config.d.ts +6 -0
- package/dist/server/config.js +4 -1
- package/dist/server/generate-config.d.ts +3 -30
- package/dist/server/generate-config.js +172 -100
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +124 -149
- package/dist/server/pollinations-api.d.ts +11 -0
- package/dist/server/pollinations-api.js +20 -0
- package/dist/server/proxy.js +158 -72
- package/dist/server/quota.d.ts +8 -0
- package/dist/server/quota.js +106 -61
- package/dist/server/toast.d.ts +3 -0
- package/dist/server/toast.js +16 -0
- 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/index.d.ts +22 -0
- package/dist/tools/index.js +81 -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 +204 -0
- package/dist/tools/pollinations/gen_image.d.ts +13 -0
- package/dist/tools/pollinations/gen_image.js +239 -0
- package/dist/tools/pollinations/gen_music.d.ts +14 -0
- package/dist/tools/pollinations/gen_music.js +139 -0
- package/dist/tools/pollinations/gen_video.d.ts +16 -0
- package/dist/tools/pollinations/gen_video.js +222 -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 +170 -0
- package/dist/tools/pollinations/shared.js +454 -0
- package/dist/tools/pollinations/transcribe_audio.d.ts +17 -0
- package/dist/tools/pollinations/transcribe_audio.js +235 -0
- package/dist/tools/power/extract_audio.d.ts +2 -0
- package/dist/tools/power/extract_audio.js +180 -0
- package/dist/tools/power/extract_frames.d.ts +2 -0
- package/dist/tools/power/extract_frames.js +240 -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 +365 -0
- package/dist/tools/power/rmbg_keys.d.ts +2 -0
- package/dist/tools/power/rmbg_keys.js +78 -0
- package/dist/tools/shared.d.ts +30 -0
- package/dist/tools/shared.js +74 -0
- package/package.json +9 -3
- package/dist/server/models-seed.d.ts +0 -18
- package/dist/server/models-seed.js +0 -55
package/dist/server/toast.js
CHANGED
|
@@ -76,3 +76,19 @@ export function createToastHooks(client) {
|
|
|
76
76
|
}
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
+
// 3. CANAL TOOLS (Natif)
|
|
80
|
+
export function createToolHooks(client) {
|
|
81
|
+
return {
|
|
82
|
+
'tool.execute.after': async (input, output) => {
|
|
83
|
+
// Check for metadata in the output
|
|
84
|
+
if (output.metadata && output.metadata.message) {
|
|
85
|
+
const meta = output.metadata;
|
|
86
|
+
const type = meta.type || 'info';
|
|
87
|
+
// If title is not in metadata, try to use the one from output or default
|
|
88
|
+
const title = meta.title || output.title || 'Pollinations Tool';
|
|
89
|
+
// Emit the toast
|
|
90
|
+
emitStatusToast(type, meta.message, title);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
+
import * as https from 'https';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { resolveOutputDir, TOOL_DIRS } from '../shared.js';
|
|
6
|
+
const MERMAID_INK_BASE = 'https://mermaid.ink';
|
|
7
|
+
/**
|
|
8
|
+
* Encode Mermaid code for mermaid.ink API
|
|
9
|
+
* Uses base64 encoding of the diagram definition
|
|
10
|
+
*/
|
|
11
|
+
function encodeMermaid(code) {
|
|
12
|
+
return Buffer.from(code, 'utf-8').toString('base64url');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Fetch binary content from URL
|
|
16
|
+
*/
|
|
17
|
+
function fetchBinary(url) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const req = https.get(url, { headers: { 'User-Agent': 'OpenCode-Pollinations-Plugin/6.0' } }, (res) => {
|
|
20
|
+
// Follow redirects
|
|
21
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
22
|
+
return fetchBinary(res.headers.location).then(resolve).catch(reject);
|
|
23
|
+
}
|
|
24
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
25
|
+
return reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
|
|
26
|
+
}
|
|
27
|
+
const chunks = [];
|
|
28
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
29
|
+
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
30
|
+
});
|
|
31
|
+
req.on('error', reject);
|
|
32
|
+
req.setTimeout(15000, () => {
|
|
33
|
+
req.destroy();
|
|
34
|
+
reject(new Error('Timeout fetching diagram'));
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export const genDiagramTool = tool({
|
|
39
|
+
description: `Render a Mermaid diagram to SVG or PNG image.
|
|
40
|
+
Uses mermaid.ink (free, no auth required). Supports all Mermaid syntax:
|
|
41
|
+
flowchart, sequenceDiagram, classDiagram, stateDiagram, erDiagram, gantt, pie, mindmap, timeline, etc.
|
|
42
|
+
The diagram code should be valid Mermaid syntax WITHOUT the \`\`\`mermaid fences.`,
|
|
43
|
+
args: {
|
|
44
|
+
code: tool.schema.string().describe('Mermaid diagram code (e.g. "graph LR; A-->B; B-->C")'),
|
|
45
|
+
format: tool.schema.enum(['svg', 'png']).optional().describe('Output format (default: svg)'),
|
|
46
|
+
theme: tool.schema.enum(['default', 'dark', 'forest', 'neutral']).optional().describe('Diagram theme (default: default)'),
|
|
47
|
+
filename: tool.schema.string().optional().describe('Custom filename (without extension). Auto-generated if omitted'),
|
|
48
|
+
output_path: tool.schema.string().optional().describe('Custom output directory. Default: ~/Downloads/pollinations/diagrams/'),
|
|
49
|
+
},
|
|
50
|
+
async execute(args, context) {
|
|
51
|
+
const format = args.format || 'svg';
|
|
52
|
+
const theme = args.theme || 'default';
|
|
53
|
+
const outputDir = resolveOutputDir(TOOL_DIRS.diagrams, args.output_path);
|
|
54
|
+
// Build mermaid.ink URL
|
|
55
|
+
// For themed rendering, we wrap with config
|
|
56
|
+
const themedCode = theme !== 'default'
|
|
57
|
+
? `%%{init: {'theme': '${theme}'}}%%\n${args.code}`
|
|
58
|
+
: args.code;
|
|
59
|
+
const encoded = encodeMermaid(themedCode);
|
|
60
|
+
const endpoint = format === 'svg' ? 'svg' : 'img';
|
|
61
|
+
const url = `${MERMAID_INK_BASE}/${endpoint}/${encoded}`;
|
|
62
|
+
// Generate filename
|
|
63
|
+
const safeName = args.filename
|
|
64
|
+
? args.filename.replace(/[^a-zA-Z0-9_-]/g, '_')
|
|
65
|
+
: `diagram_${Date.now()}`;
|
|
66
|
+
const filePath = path.join(outputDir, `${safeName}.${format}`);
|
|
67
|
+
try {
|
|
68
|
+
const data = await fetchBinary(url);
|
|
69
|
+
if (data.length < 50) {
|
|
70
|
+
return `❌ Diagram Error: mermaid.ink returned empty/invalid response. Check your Mermaid syntax.`;
|
|
71
|
+
}
|
|
72
|
+
fs.writeFileSync(filePath, data);
|
|
73
|
+
const fileSizeKB = (data.length / 1024).toFixed(1);
|
|
74
|
+
// Extract diagram type from first line
|
|
75
|
+
const firstLine = args.code.trim().split('\n')[0].trim();
|
|
76
|
+
const diagramType = firstLine.replace(/[;\s{].*/g, '');
|
|
77
|
+
context.metadata({ title: `📊 Diagram: ${diagramType}` });
|
|
78
|
+
return [
|
|
79
|
+
`📊 Diagram Rendered`,
|
|
80
|
+
`━━━━━━━━━━━━━━━━━━━`,
|
|
81
|
+
`Type: ${diagramType}`,
|
|
82
|
+
`Theme: ${theme}`,
|
|
83
|
+
`Format: ${format.toUpperCase()}`,
|
|
84
|
+
`File: ${filePath}`,
|
|
85
|
+
`Weight: ${fileSizeKB} KB`,
|
|
86
|
+
`URL: ${url}`,
|
|
87
|
+
`Cost: Free (mermaid.ink)`,
|
|
88
|
+
].join('\n');
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
return `❌ Diagram Error: ${err.message}\n💡 Verify your Mermaid syntax at https://mermaid.live`;
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
});
|
|
@@ -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,22 @@
|
|
|
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 { genImageTool } from './pollinations/gen_image.js';
|
|
10
|
+
import { genVideoTool } from './pollinations/gen_video.js';
|
|
11
|
+
import { genAudioTool } from './pollinations/gen_audio.js';
|
|
12
|
+
import { transcribeAudioTool } from './pollinations/transcribe_audio.js';
|
|
13
|
+
import { genMusicTool } from './pollinations/gen_music.js';
|
|
14
|
+
import { deepsearchTool } from './pollinations/deepsearch.js';
|
|
15
|
+
import { searchCrawlScrapeTool } from './pollinations/search_crawl_scrape.js';
|
|
16
|
+
/**
|
|
17
|
+
* Build the tool registry based on user's access level
|
|
18
|
+
*
|
|
19
|
+
* @returns Record<string, Tool> to be spread into the plugin's tool: {} property
|
|
20
|
+
*/
|
|
21
|
+
export declare function createToolRegistry(): Record<string, any>;
|
|
22
|
+
export { genImageTool, genVideoTool, genAudioTool, transcribeAudioTool, genMusicTool, deepsearchTool, searchCrawlScrapeTool, };
|
|
@@ -0,0 +1,81 @@
|
|
|
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 { genImageTool } from './pollinations/gen_image.js';
|
|
21
|
+
import { genVideoTool } from './pollinations/gen_video.js';
|
|
22
|
+
import { genAudioTool } from './pollinations/gen_audio.js';
|
|
23
|
+
import { transcribeAudioTool } from './pollinations/transcribe_audio.js';
|
|
24
|
+
import { genMusicTool } from './pollinations/gen_music.js';
|
|
25
|
+
import { deepsearchTool } from './pollinations/deepsearch.js';
|
|
26
|
+
import { searchCrawlScrapeTool } from './pollinations/search_crawl_scrape.js';
|
|
27
|
+
import * as fs from 'fs';
|
|
28
|
+
const LOG_FILE = '/tmp/opencode_pollinations_v4.log';
|
|
29
|
+
function log(msg) {
|
|
30
|
+
try {
|
|
31
|
+
fs.appendFileSync(LOG_FILE, `[${new Date().toISOString()}] [Tools] ${msg}\n`);
|
|
32
|
+
}
|
|
33
|
+
catch { }
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Detect if a valid API key is present
|
|
37
|
+
*/
|
|
38
|
+
function hasValidKey() {
|
|
39
|
+
const config = loadConfig();
|
|
40
|
+
return !!(config.apiKey && config.apiKey.length > 5 && config.apiKey !== 'dummy');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build the tool registry based on user's access level
|
|
44
|
+
*
|
|
45
|
+
* @returns Record<string, Tool> to be spread into the plugin's tool: {} property
|
|
46
|
+
*/
|
|
47
|
+
export function createToolRegistry() {
|
|
48
|
+
const tools = {};
|
|
49
|
+
const keyPresent = hasValidKey();
|
|
50
|
+
// === FREE UNIVERSE: Always injected (8 tools) ===
|
|
51
|
+
// Design tools (3)
|
|
52
|
+
tools['gen_qrcode'] = genQrcodeTool;
|
|
53
|
+
tools['gen_diagram'] = genDiagramTool;
|
|
54
|
+
tools['gen_palette'] = genPaletteTool;
|
|
55
|
+
// Power tools (5)
|
|
56
|
+
tools['file_to_url'] = fileToUrlTool;
|
|
57
|
+
tools['remove_background'] = removeBackgroundTool;
|
|
58
|
+
tools['extract_frames'] = extractFramesTool;
|
|
59
|
+
tools['extract_audio'] = extractAudioTool;
|
|
60
|
+
tools['rmbg_keys'] = rmbgKeysTool;
|
|
61
|
+
log(`Free tools injected: ${Object.keys(tools).length}`);
|
|
62
|
+
// === ENTER UNIVERSE: Only with valid API key (+7 tools) ===
|
|
63
|
+
if (keyPresent) {
|
|
64
|
+
// Pollinations media tools
|
|
65
|
+
tools['gen_image'] = genImageTool;
|
|
66
|
+
tools['gen_video'] = genVideoTool;
|
|
67
|
+
tools['gen_audio'] = genAudioTool;
|
|
68
|
+
tools['transcribe_audio'] = transcribeAudioTool;
|
|
69
|
+
tools['gen_music'] = genMusicTool;
|
|
70
|
+
// Search tools
|
|
71
|
+
tools['deepsearch'] = deepsearchTool;
|
|
72
|
+
tools['search_crawl_scrape'] = searchCrawlScrapeTool;
|
|
73
|
+
log(`Enter tools injected (key detected). Total: ${Object.keys(tools).length}`);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
log(`Enter tools SKIPPED (no key). Total: ${Object.keys(tools).length}`);
|
|
77
|
+
}
|
|
78
|
+
return tools;
|
|
79
|
+
}
|
|
80
|
+
// Re-export for convenience
|
|
81
|
+
export { genImageTool, genVideoTool, genAudioTool, transcribeAudioTool, genMusicTool, deepsearchTool, searchCrawlScrapeTool, };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* deepsearch Tool - Deep Research with AI
|
|
3
|
+
*
|
|
4
|
+
* Uses perplexity-reasoning for in-depth research and analysis
|
|
5
|
+
*/
|
|
6
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
7
|
+
import { getApiKey, httpsPost, } from './shared.js';
|
|
8
|
+
// ─── Tool Definition ──────────────────────────────────────────────────────
|
|
9
|
+
export const deepsearchTool = tool({
|
|
10
|
+
description: `Perform deep research and analysis on a topic using AI reasoning.
|
|
11
|
+
|
|
12
|
+
**Model:** perplexity-reasoning
|
|
13
|
+
|
|
14
|
+
This tool provides comprehensive research with:
|
|
15
|
+
- Multi-step reasoning
|
|
16
|
+
- Source citations
|
|
17
|
+
- In-depth analysis
|
|
18
|
+
- Fact verification
|
|
19
|
+
|
|
20
|
+
**Use for:**
|
|
21
|
+
- Complex research questions
|
|
22
|
+
- Technical analysis
|
|
23
|
+
- Fact-checking
|
|
24
|
+
- Comparative studies
|
|
25
|
+
|
|
26
|
+
**Cost:** ~0.000002-0.000008 🌻 per token (very affordable)`,
|
|
27
|
+
args: {
|
|
28
|
+
query: tool.schema.string().describe('Research query or question to investigate'),
|
|
29
|
+
depth: tool.schema.enum(['quick', 'standard', 'thorough']).optional()
|
|
30
|
+
.describe('Research depth (default: standard)'),
|
|
31
|
+
},
|
|
32
|
+
async execute(args, context) {
|
|
33
|
+
const apiKey = getApiKey();
|
|
34
|
+
if (!apiKey) {
|
|
35
|
+
return `❌ Deep Search nécessite une clé API Pollinations.
|
|
36
|
+
🔧 Connectez votre clé avec /pollinations connect`;
|
|
37
|
+
}
|
|
38
|
+
const model = 'perplexity-reasoning';
|
|
39
|
+
const depth = args.depth || 'standard';
|
|
40
|
+
// Metadata
|
|
41
|
+
context.metadata({ title: `🔍 Deep Search: ${args.query.substring(0, 50)}...` });
|
|
42
|
+
try {
|
|
43
|
+
// Build system prompt based on depth
|
|
44
|
+
const systemPrompts = {
|
|
45
|
+
quick: 'Provide a concise but thorough answer with key sources. Be efficient.',
|
|
46
|
+
standard: 'Provide comprehensive research with analysis, sources, and reasoning steps.',
|
|
47
|
+
thorough: 'Provide exhaustive research with multiple perspectives, detailed analysis, all relevant sources, and thorough fact-checking. Consider edge cases and alternative viewpoints.',
|
|
48
|
+
};
|
|
49
|
+
const { data } = await httpsPost('https://gen.pollinations.ai/v1/chat/completions', {
|
|
50
|
+
model: model,
|
|
51
|
+
messages: [
|
|
52
|
+
{ role: 'system', content: systemPrompts[depth] },
|
|
53
|
+
{ role: 'user', content: args.query },
|
|
54
|
+
],
|
|
55
|
+
max_tokens: depth === 'thorough' ? 8000 : depth === 'standard' ? 4000 : 2000,
|
|
56
|
+
}, {
|
|
57
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
58
|
+
});
|
|
59
|
+
const jsonData = JSON.parse(data.toString());
|
|
60
|
+
const content = jsonData.choices?.[0]?.message?.content || 'No response';
|
|
61
|
+
// Format result
|
|
62
|
+
const lines = [
|
|
63
|
+
`🔍 Deep Search Results`,
|
|
64
|
+
`━━━━━━━━━━━━━━━━━━`,
|
|
65
|
+
`Query: ${args.query}`,
|
|
66
|
+
`Depth: ${depth}`,
|
|
67
|
+
`Model: ${model}`,
|
|
68
|
+
``,
|
|
69
|
+
content,
|
|
70
|
+
];
|
|
71
|
+
return lines.join('\n');
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
if (err.message?.includes('402') || err.message?.includes('Payment')) {
|
|
75
|
+
return `❌ Crédits insuffisants.`;
|
|
76
|
+
}
|
|
77
|
+
return `❌ Erreur Deep Search: ${err.message}`;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gen_audio Tool - Pollinations Text-to-Speech
|
|
3
|
+
*
|
|
4
|
+
* Updated: 2026-02-12 - Verified API Reference
|
|
5
|
+
*
|
|
6
|
+
* Two TTS options:
|
|
7
|
+
* 1. openai-audio (DEFAULT): GPT-4o Audio Preview - uses /v1/chat/completions with modalities
|
|
8
|
+
* - Supports both TTS and STT (Speech-to-Text)
|
|
9
|
+
* - Least expensive option
|
|
10
|
+
* - Voices: alloy, echo, fable, onyx, nova, shimmer
|
|
11
|
+
* - Formats: mp3, wav, pcm16
|
|
12
|
+
*
|
|
13
|
+
* 2. elevenlabs: ElevenLabs v3 TTS - uses /audio/{text}
|
|
14
|
+
* - 34 expressive voices
|
|
15
|
+
* - Higher quality but more expensive
|
|
16
|
+
*/
|
|
17
|
+
import { type ToolDefinition } from '@opencode-ai/plugin/tool';
|
|
18
|
+
export declare const genAudioTool: ToolDefinition;
|