opencode-nanobanana 0.1.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/.ralph-events.json +151 -0
- package/.ralph-last-branch +1 -0
- package/.ralph-monitor-state.json +7 -0
- package/.ralph-monitor.pid +1 -0
- package/.ralph-timing.json +26 -0
- package/README.md +708 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/platforms/android.d.ts +94 -0
- package/dist/platforms/android.d.ts.map +1 -0
- package/dist/platforms/android.js +123 -0
- package/dist/platforms/android.js.map +1 -0
- package/dist/platforms/ios.d.ts +51 -0
- package/dist/platforms/ios.d.ts.map +1 -0
- package/dist/platforms/ios.js +149 -0
- package/dist/platforms/ios.js.map +1 -0
- package/dist/platforms/macos.d.ts +33 -0
- package/dist/platforms/macos.d.ts.map +1 -0
- package/dist/platforms/macos.js +50 -0
- package/dist/platforms/macos.js.map +1 -0
- package/dist/platforms/watchos.d.ts +36 -0
- package/dist/platforms/watchos.d.ts.map +1 -0
- package/dist/platforms/watchos.js +113 -0
- package/dist/platforms/watchos.js.map +1 -0
- package/dist/platforms/web.d.ts +64 -0
- package/dist/platforms/web.d.ts.map +1 -0
- package/dist/platforms/web.js +96 -0
- package/dist/platforms/web.js.map +1 -0
- package/dist/providers/gemini.d.ts +41 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +177 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/tools/analyze/compare.d.ts +12 -0
- package/dist/tools/analyze/compare.d.ts.map +1 -0
- package/dist/tools/analyze/compare.js +83 -0
- package/dist/tools/analyze/compare.js.map +1 -0
- package/dist/tools/analyze/mockup.d.ts +12 -0
- package/dist/tools/analyze/mockup.d.ts.map +1 -0
- package/dist/tools/analyze/mockup.js +88 -0
- package/dist/tools/analyze/mockup.js.map +1 -0
- package/dist/tools/analyze/screenshot.d.ts +12 -0
- package/dist/tools/analyze/screenshot.d.ts.map +1 -0
- package/dist/tools/analyze/screenshot.js +61 -0
- package/dist/tools/analyze/screenshot.js.map +1 -0
- package/dist/tools/app-assets/app-icon.d.ts +9 -0
- package/dist/tools/app-assets/app-icon.d.ts.map +1 -0
- package/dist/tools/app-assets/app-icon.js +133 -0
- package/dist/tools/app-assets/app-icon.js.map +1 -0
- package/dist/tools/app-assets/device-mockup.d.ts +9 -0
- package/dist/tools/app-assets/device-mockup.d.ts.map +1 -0
- package/dist/tools/app-assets/device-mockup.js +139 -0
- package/dist/tools/app-assets/device-mockup.js.map +1 -0
- package/dist/tools/app-assets/launch-images.d.ts +3 -0
- package/dist/tools/app-assets/launch-images.d.ts.map +1 -0
- package/dist/tools/app-assets/launch-images.js +171 -0
- package/dist/tools/app-assets/launch-images.js.map +1 -0
- package/dist/tools/app-assets/resize-devices.d.ts +14 -0
- package/dist/tools/app-assets/resize-devices.d.ts.map +1 -0
- package/dist/tools/app-assets/resize-devices.js +296 -0
- package/dist/tools/app-assets/resize-devices.js.map +1 -0
- package/dist/tools/app-assets/screenshots.d.ts +14 -0
- package/dist/tools/app-assets/screenshots.d.ts.map +1 -0
- package/dist/tools/app-assets/screenshots.js +186 -0
- package/dist/tools/app-assets/screenshots.js.map +1 -0
- package/dist/tools/core/edit-image.d.ts +12 -0
- package/dist/tools/core/edit-image.d.ts.map +1 -0
- package/dist/tools/core/edit-image.js +102 -0
- package/dist/tools/core/edit-image.js.map +1 -0
- package/dist/tools/core/generate-image.d.ts +12 -0
- package/dist/tools/core/generate-image.d.ts.map +1 -0
- package/dist/tools/core/generate-image.js +96 -0
- package/dist/tools/core/generate-image.js.map +1 -0
- package/dist/tools/core/restore-image.d.ts +12 -0
- package/dist/tools/core/restore-image.d.ts.map +1 -0
- package/dist/tools/core/restore-image.js +104 -0
- package/dist/tools/core/restore-image.js.map +1 -0
- package/dist/tools/design/mockup-to-code.d.ts +3 -0
- package/dist/tools/design/mockup-to-code.d.ts.map +1 -0
- package/dist/tools/design/mockup-to-code.js +311 -0
- package/dist/tools/design/mockup-to-code.js.map +1 -0
- package/dist/tools/design/sketch-to-code.d.ts +3 -0
- package/dist/tools/design/sketch-to-code.d.ts.map +1 -0
- package/dist/tools/design/sketch-to-code.js +325 -0
- package/dist/tools/design/sketch-to-code.js.map +1 -0
- package/dist/tools/docs/architecture-diagram.d.ts +12 -0
- package/dist/tools/docs/architecture-diagram.d.ts.map +1 -0
- package/dist/tools/docs/architecture-diagram.js +179 -0
- package/dist/tools/docs/architecture-diagram.js.map +1 -0
- package/dist/tools/docs/readme-banner.d.ts +6 -0
- package/dist/tools/docs/readme-banner.d.ts.map +1 -0
- package/dist/tools/docs/readme-banner.js +108 -0
- package/dist/tools/docs/readme-banner.js.map +1 -0
- package/dist/tools/docs/sequence-diagram.d.ts +12 -0
- package/dist/tools/docs/sequence-diagram.d.ts.map +1 -0
- package/dist/tools/docs/sequence-diagram.js +161 -0
- package/dist/tools/docs/sequence-diagram.js.map +1 -0
- package/dist/tools/docs/social-preview.d.ts +11 -0
- package/dist/tools/docs/social-preview.d.ts.map +1 -0
- package/dist/tools/docs/social-preview.js +111 -0
- package/dist/tools/docs/social-preview.js.map +1 -0
- package/dist/tools/video/extend-video.d.ts +14 -0
- package/dist/tools/video/extend-video.d.ts.map +1 -0
- package/dist/tools/video/extend-video.js +39 -0
- package/dist/tools/video/extend-video.js.map +1 -0
- package/dist/tools/video/generate-video.d.ts +14 -0
- package/dist/tools/video/generate-video.d.ts.map +1 -0
- package/dist/tools/video/generate-video.js +39 -0
- package/dist/tools/video/generate-video.js.map +1 -0
- package/dist/tools/video/image-to-video.d.ts +15 -0
- package/dist/tools/video/image-to-video.d.ts.map +1 -0
- package/dist/tools/video/image-to-video.js +42 -0
- package/dist/tools/video/image-to-video.js.map +1 -0
- package/dist/tools/video/storyboard-video.d.ts +91 -0
- package/dist/tools/video/storyboard-video.d.ts.map +1 -0
- package/dist/tools/video/storyboard-video.js +230 -0
- package/dist/tools/video/storyboard-video.js.map +1 -0
- package/dist/utils/ffmpeg.d.ts +30 -0
- package/dist/utils/ffmpeg.d.ts.map +1 -0
- package/dist/utils/ffmpeg.js +205 -0
- package/dist/utils/ffmpeg.js.map +1 -0
- package/dist/utils/file-handler.d.ts +7 -0
- package/dist/utils/file-handler.d.ts.map +1 -0
- package/dist/utils/file-handler.js +10 -0
- package/dist/utils/file-handler.js.map +1 -0
- package/dist/utils/image-processing.d.ts +7 -0
- package/dist/utils/image-processing.d.ts.map +1 -0
- package/dist/utils/image-processing.js +10 -0
- package/dist/utils/image-processing.js.map +1 -0
- package/docs/PLUGIN-VERIFICATION.md +182 -0
- package/logs/notifications.jsonl +46 -0
- package/package.json +61 -0
- package/prd.json +216 -0
- package/progress.txt +145 -0
- package/ralph-report.html +297 -0
- package/src/index.ts +23 -0
- package/src/platforms/android/.gitkeep +0 -0
- package/src/platforms/ios/.gitkeep +0 -0
- package/src/platforms/web/.gitkeep +0 -0
- package/src/providers/.gitkeep +0 -0
- package/src/providers/gemini.ts +288 -0
- package/src/tools/core/.gitkeep +0 -0
- package/src/tools/platform/.gitkeep +0 -0
- package/src/tools/video/extend-video.ts +71 -0
- package/src/tools/video/generate-video.ts +70 -0
- package/src/tools/video/image-to-video.ts +76 -0
- package/src/tools/video/storyboard-video.ts +325 -0
- package/src/utils/.gitkeep +0 -0
- package/src/utils/ffmpeg.ts +266 -0
- package/src/utils/file-handler.ts +10 -0
- package/src/utils/image-processing.ts +10 -0
- package/templates/.gitkeep +0 -0
- package/test-analyze-screenshot.ts +50 -0
- package/test-app-icons.ts +55 -0
- package/test-cat-sunset.ts +30 -0
- package/test-full-plugin.ts +88 -0
- package/test-icon-gen.ts +30 -0
- package/test-output/test-edit.png +0 -0
- package/test-output/test-generate.png +0 -0
- package/test-output/test-video.mp4 +0 -0
- package/test-plugin-load.js +45 -0
- package/test-princess-emma-continue.ts +35 -0
- package/test-princess-emma-full.ts +38 -0
- package/test-princess-emma-short.ts +32 -0
- package/test-princess-emma-with-reference.ts +34 -0
- package/test-princess-emma.ts +38 -0
- package/test-product-ad.ts +66 -0
- package/test-ralph-droid.ts +30 -0
- package/test-social-preview.ts +61 -0
- package/test-veo31-live.ts +187 -0
- package/test-video-gen.ts +40 -0
- package/test-video-veo.ts +73 -0
- package/test-zurich-video.ts +64 -0
- package/tests/.gitkeep +0 -0
- package/tests/providers/gemini.test.ts +388 -0
- package/tests/utils/ffmpeg.test.ts +328 -0
- package/tests/video/storyboard.test.ts +469 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { GeminiProvider } from './src/providers/gemini.js';
|
|
2
|
+
import { saveImage } from './src/utils/file-handler.js';
|
|
3
|
+
import { IOS_ICON_SIZES } from './src/platforms/ios.js';
|
|
4
|
+
import * as fs from 'fs/promises';
|
|
5
|
+
import sharp from 'sharp';
|
|
6
|
+
|
|
7
|
+
async function generateAppIcons() {
|
|
8
|
+
console.log('š Generating iOS App Icon Set - Blue Rocket Ship\n');
|
|
9
|
+
|
|
10
|
+
const provider = new GeminiProvider();
|
|
11
|
+
const outputDir = './generated-assets/ios-rocket-icons';
|
|
12
|
+
|
|
13
|
+
// Create output directory
|
|
14
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
15
|
+
|
|
16
|
+
const prompt = 'A sleek blue rocket ship icon on a beautiful gradient background, purple to blue gradient, minimalist flat design, app icon style, centered composition, clean and modern';
|
|
17
|
+
|
|
18
|
+
console.log('š Prompt:', prompt);
|
|
19
|
+
console.log('ā³ Generating master icon (1024x1024)...\n');
|
|
20
|
+
|
|
21
|
+
const startTime = Date.now();
|
|
22
|
+
const images = await provider.generateImage(prompt, {
|
|
23
|
+
aspectRatio: '1:1',
|
|
24
|
+
count: 1
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const masterPath = `${outputDir}/icon-1024.png`;
|
|
28
|
+
await fs.writeFile(masterPath, images[0]);
|
|
29
|
+
console.log(`ā
Master icon saved: ${masterPath}`);
|
|
30
|
+
|
|
31
|
+
console.log('\nš± Generating all iOS sizes...\n');
|
|
32
|
+
|
|
33
|
+
// Generate all iOS icon sizes
|
|
34
|
+
for (const iconSize of IOS_ICON_SIZES) {
|
|
35
|
+
const size = iconSize.size;
|
|
36
|
+
const scale = iconSize.scale;
|
|
37
|
+
const pixelSize = size * scale;
|
|
38
|
+
const filename = `icon-${size}@${scale}x.png`;
|
|
39
|
+
const filepath = `${outputDir}/${filename}`;
|
|
40
|
+
|
|
41
|
+
await sharp(images[0])
|
|
42
|
+
.resize(pixelSize, pixelSize, { fit: 'fill' })
|
|
43
|
+
.png()
|
|
44
|
+
.toFile(filepath);
|
|
45
|
+
|
|
46
|
+
console.log(` ā
${filename} (${pixelSize}x${pixelSize}px) - ${iconSize.idiom}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
50
|
+
|
|
51
|
+
console.log(`\nš Done! Generated ${IOS_ICON_SIZES.length + 1} icons in ${duration}s`);
|
|
52
|
+
console.log(`š Location: ${outputDir}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
generateAppIcons().catch(console.error);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { GeminiProvider } from './src/providers/gemini.js';
|
|
2
|
+
import { saveImage } from './src/utils/file-handler.js';
|
|
3
|
+
|
|
4
|
+
async function generateCatSunset() {
|
|
5
|
+
console.log('š± Generating cat at sunset beach...\n');
|
|
6
|
+
|
|
7
|
+
const provider = new GeminiProvider();
|
|
8
|
+
|
|
9
|
+
const prompt = 'A silhouette of a cat sitting peacefully on a sandy beach, facing a beautiful vibrant sunset over the ocean, warm orange and pink sky colors reflecting on calm waves, golden hour lighting, serene and tranquil atmosphere, photorealistic style, wide-angle composition';
|
|
10
|
+
|
|
11
|
+
console.log('š Prompt:', prompt);
|
|
12
|
+
console.log('ā³ Generating...\n');
|
|
13
|
+
|
|
14
|
+
const startTime = Date.now();
|
|
15
|
+
const images = await provider.generateImage(prompt, {
|
|
16
|
+
aspectRatio: '16:9',
|
|
17
|
+
count: 1
|
|
18
|
+
});
|
|
19
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
20
|
+
|
|
21
|
+
console.log(`ā
Generated in ${duration}s`);
|
|
22
|
+
console.log(`š¦ Size: ${(images[0].length / 1024).toFixed(1)}KB\n`);
|
|
23
|
+
|
|
24
|
+
const outputPath = await saveImage(images[0], './generated-assets', 'cat-sunset-beach', 0);
|
|
25
|
+
console.log(`š¾ Saved to: ${outputPath}`);
|
|
26
|
+
|
|
27
|
+
console.log('\nš Done! Your peaceful sunset scene is ready.');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
generateCatSunset().catch(console.error);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { GeminiProvider } from './src/providers/gemini.js';
|
|
2
|
+
import { saveImage } from './src/utils/file-handler.js';
|
|
3
|
+
import * as fs from 'fs/promises';
|
|
4
|
+
|
|
5
|
+
async function runTests() {
|
|
6
|
+
console.log('š§Ŗ FULL PLUGIN TEST SUITE\n');
|
|
7
|
+
console.log('=' .repeat(50));
|
|
8
|
+
|
|
9
|
+
const provider = new GeminiProvider();
|
|
10
|
+
const results: {test: string, status: string, time: string}[] = [];
|
|
11
|
+
|
|
12
|
+
// Test 1: Image Generation
|
|
13
|
+
console.log('\n1ļøā£ Testing generate_image...');
|
|
14
|
+
try {
|
|
15
|
+
const start = Date.now();
|
|
16
|
+
const images = await provider.generateImage('A simple blue circle icon, minimal', { count: 1 });
|
|
17
|
+
const time = ((Date.now() - start) / 1000).toFixed(1);
|
|
18
|
+
await saveImage(images[0], './test-output', 'test-generate', 0);
|
|
19
|
+
results.push({test: 'generate_image', status: 'ā
PASS', time: `${time}s`});
|
|
20
|
+
console.log(` ā
Generated image in ${time}s`);
|
|
21
|
+
} catch (e: any) {
|
|
22
|
+
results.push({test: 'generate_image', status: 'ā FAIL', time: e.message});
|
|
23
|
+
console.log(` ā Failed: ${e.message}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Test 2: Image Analysis
|
|
27
|
+
console.log('\n2ļøā£ Testing analyze_image...');
|
|
28
|
+
try {
|
|
29
|
+
const start = Date.now();
|
|
30
|
+
const testImage = await fs.readFile('./test-output/test-generate.png');
|
|
31
|
+
const analysis = await provider.analyzeImage(testImage, 'What color is this shape?');
|
|
32
|
+
const time = ((Date.now() - start) / 1000).toFixed(1);
|
|
33
|
+
results.push({test: 'analyze_image', status: 'ā
PASS', time: `${time}s`});
|
|
34
|
+
console.log(` ā
Analyzed in ${time}s: "${analysis.substring(0, 50)}..."`);
|
|
35
|
+
} catch (e: any) {
|
|
36
|
+
results.push({test: 'analyze_image', status: 'ā FAIL', time: e.message});
|
|
37
|
+
console.log(` ā Failed: ${e.message}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Test 3: Image Editing
|
|
41
|
+
console.log('\n3ļøā£ Testing edit_image...');
|
|
42
|
+
try {
|
|
43
|
+
const start = Date.now();
|
|
44
|
+
const testImage = await fs.readFile('./test-output/test-generate.png');
|
|
45
|
+
const edited = await provider.editImage(testImage, 'Make the circle red instead of blue');
|
|
46
|
+
const time = ((Date.now() - start) / 1000).toFixed(1);
|
|
47
|
+
await saveImage(edited, './test-output', 'test-edit', 0);
|
|
48
|
+
results.push({test: 'edit_image', status: 'ā
PASS', time: `${time}s`});
|
|
49
|
+
console.log(` ā
Edited image in ${time}s`);
|
|
50
|
+
} catch (e: any) {
|
|
51
|
+
results.push({test: 'edit_image', status: 'ā FAIL', time: e.message});
|
|
52
|
+
console.log(` ā Failed: ${e.message}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Test 4: Video Generation
|
|
56
|
+
console.log('\n4ļøā£ Testing generate_video...');
|
|
57
|
+
try {
|
|
58
|
+
const start = Date.now();
|
|
59
|
+
const video = await provider.generateVideo('A simple animation of a bouncing ball');
|
|
60
|
+
const time = ((Date.now() - start) / 1000).toFixed(1);
|
|
61
|
+
await fs.writeFile('./test-output/test-video.mp4', video);
|
|
62
|
+
results.push({test: 'generate_video', status: 'ā
PASS', time: `${time}s`});
|
|
63
|
+
console.log(` ā
Generated video in ${time}s (${(video.length/1024/1024).toFixed(1)}MB)`);
|
|
64
|
+
} catch (e: any) {
|
|
65
|
+
results.push({test: 'generate_video', status: 'ā FAIL', time: e.message});
|
|
66
|
+
console.log(` ā Failed: ${e.message}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Summary
|
|
70
|
+
console.log('\n' + '=' .repeat(50));
|
|
71
|
+
console.log('š TEST RESULTS SUMMARY\n');
|
|
72
|
+
console.log('| Test | Status | Time |');
|
|
73
|
+
console.log('|----------------|---------|---------|');
|
|
74
|
+
for (const r of results) {
|
|
75
|
+
console.log(`| ${r.test.padEnd(14)} | ${r.status.padEnd(7)} | ${r.time.padEnd(7)} |`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const passed = results.filter(r => r.status.includes('PASS')).length;
|
|
79
|
+
console.log(`\n${passed}/${results.length} tests passed`);
|
|
80
|
+
|
|
81
|
+
if (passed === results.length) {
|
|
82
|
+
console.log('\nš ALL TESTS PASSED - Plugin is ready for production!');
|
|
83
|
+
} else {
|
|
84
|
+
console.log('\nā ļø Some tests failed - review before merging');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fs.mkdir('./test-output', { recursive: true }).then(() => runTests()).catch(console.error);
|
package/test-icon-gen.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { GeminiProvider } from './src/providers/gemini.js';
|
|
2
|
+
import { saveImage } from './src/utils/file-handler.js';
|
|
3
|
+
|
|
4
|
+
async function generateIcon() {
|
|
5
|
+
console.log('šØ Generating modern app icon...\n');
|
|
6
|
+
|
|
7
|
+
const provider = new GeminiProvider();
|
|
8
|
+
|
|
9
|
+
const prompt = 'A modern app icon with gradient colors, minimalist design, rounded square shape, professional and clean aesthetic, vibrant blue and purple gradient';
|
|
10
|
+
|
|
11
|
+
console.log('š Prompt:', prompt);
|
|
12
|
+
console.log('ā³ Generating...\n');
|
|
13
|
+
|
|
14
|
+
const startTime = Date.now();
|
|
15
|
+
const images = await provider.generateImage(prompt, {
|
|
16
|
+
aspectRatio: '1:1',
|
|
17
|
+
count: 1
|
|
18
|
+
});
|
|
19
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
20
|
+
|
|
21
|
+
console.log(`ā
Generated in ${duration}s`);
|
|
22
|
+
console.log(`š¦ Size: ${(images[0].length / 1024).toFixed(1)}KB\n`);
|
|
23
|
+
|
|
24
|
+
const outputPath = await saveImage(images[0], './generated-assets', 'modern-app-icon', 0);
|
|
25
|
+
console.log(`š¾ Saved to: ${outputPath}`);
|
|
26
|
+
|
|
27
|
+
console.log('\nš Done! Your modern app icon is ready.');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
generateIcon().catch(console.error);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Test script to verify OpenCode Visual Toolkit plugin loads correctly
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import VisualToolkitPlugin from './dist/index.js';
|
|
7
|
+
|
|
8
|
+
console.log('š§Ŗ Testing OpenCode Visual Toolkit Plugin Load...\n');
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
// Check plugin structure
|
|
12
|
+
console.log('ā Plugin imported successfully');
|
|
13
|
+
console.log(' Plugin is async function:', typeof VisualToolkitPlugin === 'function');
|
|
14
|
+
|
|
15
|
+
// Initialize the plugin (it's an async function)
|
|
16
|
+
console.log('\nš Initializing plugin...\n');
|
|
17
|
+
const pluginInstance = await VisualToolkitPlugin({});
|
|
18
|
+
|
|
19
|
+
// Count tools
|
|
20
|
+
const tools = pluginInstance.tool || {};
|
|
21
|
+
const toolCount = Object.keys(tools).length;
|
|
22
|
+
console.log(`\nā Plugin has ${toolCount} tools registered:`);
|
|
23
|
+
|
|
24
|
+
// List all tools with descriptions
|
|
25
|
+
Object.entries(tools).forEach(([name, tool]) => {
|
|
26
|
+
console.log(` ⢠${name}`);
|
|
27
|
+
if (tool.description) {
|
|
28
|
+
const desc = tool.description.substring(0, 70);
|
|
29
|
+
console.log(` ${desc}${desc.length < tool.description.length ? '...' : ''}`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
console.log('\nā
Plugin loaded successfully!\n');
|
|
34
|
+
console.log('To use in OpenCode Desktop:');
|
|
35
|
+
console.log(' 1. Restart OpenCode Desktop app');
|
|
36
|
+
console.log(' 2. Tools will be available automatically');
|
|
37
|
+
console.log(' 3. Try: "Generate a modern app icon"');
|
|
38
|
+
console.log(' 4. Or: "Create iOS app icons with a blue rocket"');
|
|
39
|
+
|
|
40
|
+
process.exit(0);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error('ā Plugin failed to load:', error.message);
|
|
43
|
+
console.error(error.stack);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { generateStoryboardVideo } from './src/tools/video/storyboard-video.js';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { mkdir } from 'fs/promises';
|
|
4
|
+
|
|
5
|
+
const API_KEY = process.env.GEMINI_API_KEY || 'AIzaSyDROqIj3wBKod1ks16MI9rwo7BZipqug9w';
|
|
6
|
+
const OUTPUT_DIR = './generated-assets/princess-emma';
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
console.log('\nš° Princess Emma - Remaining Scenes (4-7)\n');
|
|
10
|
+
|
|
11
|
+
await mkdir(OUTPUT_DIR, { recursive: true });
|
|
12
|
+
|
|
13
|
+
const result = await generateStoryboardVideo({
|
|
14
|
+
apiKey: API_KEY,
|
|
15
|
+
characterDescription: 'Princess Emma, a sweet toddler girl around 3 years old with chin-length wavy blonde hair, small pink flower hair clips on each side, big round blue-green eyes, rosy cheeks, and a gentle smile, wearing a flowing pink princess dress with puffy sleeves, accompanied by her fluffy small white dog Fifi',
|
|
16
|
+
style: 'magical fairy tale animation, soft pastel watercolor style, enchanted storybook illustration, warm lighting',
|
|
17
|
+
scenes: [
|
|
18
|
+
'Princess Emma kneels down in the tall green grass near an old stone fountain, searching carefully while her fluffy white dog Fifi sniffs around helping to look for something',
|
|
19
|
+
'Fifi the white dog barks excitedly and wags his tail as he finds a glowing blue gemstone near the fountain, Princess Emma picks it up with wonder in her eyes',
|
|
20
|
+
'Princess Emma gently hands the shimmering blue Aqua Stone to the grateful tiny blue fairy Aqua, both smiling with joy as magical sparkles surround them',
|
|
21
|
+
'The fairy Aqua waves her tiny wand and magical sparkles spread everywhere, all the flowers bloom in vibrant colors, trees sparkle with magic, butterflies appear, the garden transforms into a paradise'
|
|
22
|
+
],
|
|
23
|
+
aspectRatio: '16:9',
|
|
24
|
+
transition: 'crossfade',
|
|
25
|
+
transitionDuration: 0.8,
|
|
26
|
+
outputPath: join(OUTPUT_DIR, 'princess-emma-part2.mp4'),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
console.log(`\n⨠Part 2 complete!`);
|
|
30
|
+
console.log(`š Video: ${result.videoPath}`);
|
|
31
|
+
console.log(`ā±ļø Total time: ${(result.totalTime / 1000 / 60).toFixed(1)} minutes`);
|
|
32
|
+
console.log(`š¬ Scenes: ${result.successCount}/${result.successCount + result.failureCount}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { generateStoryboardVideo } from './src/tools/video/storyboard-video.js';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { mkdir } from 'fs/promises';
|
|
4
|
+
|
|
5
|
+
const API_KEY = process.env.GEMINI_API_KEY || 'AIzaSyDROqIj3wBKod1ks16MI9rwo7BZipqug9w';
|
|
6
|
+
const OUTPUT_DIR = './generated-assets/princess-emma';
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
console.log('\nš° Princess Emma and Fifi - Full Story\n');
|
|
10
|
+
|
|
11
|
+
await mkdir(OUTPUT_DIR, { recursive: true });
|
|
12
|
+
|
|
13
|
+
const result = await generateStoryboardVideo({
|
|
14
|
+
apiKey: API_KEY,
|
|
15
|
+
characterDescription: 'Princess Emma, a sweet toddler girl around 3 years old with chin-length wavy blonde hair, small pink flower hair clips on each side, big round blue-green eyes, rosy cheeks, and a gentle smile, wearing a flowing pink princess dress with puffy sleeves, accompanied by her fluffy small white dog Fifi',
|
|
16
|
+
style: 'magical fairy tale animation, soft pastel watercolor style, enchanted storybook illustration, warm lighting',
|
|
17
|
+
scenes: [
|
|
18
|
+
'A beautiful white castle with bright pink cone-shaped roofs and towers, surrounded by lush green gardens with colorful flowers and tiny glowing fairies flying around in the morning sunlight',
|
|
19
|
+
'Princess Emma and her fluffy white dog Fifi happily walk out through the grand castle doors into the sunny magical garden, birds singing in the trees',
|
|
20
|
+
'A tiny blue fairy named Aqua with sparkling translucent wings flies up to Princess Emma looking worried, pointing at the tall grass and explaining something',
|
|
21
|
+
'Princess Emma kneels down in the tall green grass near an old stone fountain, searching carefully while Fifi sniffs around helping to look',
|
|
22
|
+
'Fifi barks excitedly and wags his tail as he finds a glowing blue gemstone near the fountain, Princess Emma picks it up with wonder in her eyes',
|
|
23
|
+
'Princess Emma gently hands the shimmering blue Aqua Stone to the grateful fairy Aqua, both smiling with joy as sparkles surround them',
|
|
24
|
+
'The fairy Aqua waves her tiny wand and magical sparkles spread everywhere, all the flowers bloom in vibrant colors, trees sparkle with magic, butterflies appear, the garden transforms into a paradise'
|
|
25
|
+
],
|
|
26
|
+
aspectRatio: '16:9',
|
|
27
|
+
transition: 'crossfade',
|
|
28
|
+
transitionDuration: 0.8,
|
|
29
|
+
outputPath: join(OUTPUT_DIR, 'princess-emma-full-story.mp4'),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log(`\n⨠Story complete!`);
|
|
33
|
+
console.log(`š Video: ${result.videoPath}`);
|
|
34
|
+
console.log(`ā±ļø Total time: ${(result.totalTime / 1000 / 60).toFixed(1)} minutes`);
|
|
35
|
+
console.log(`š¬ Scenes: ${result.successCount}/${result.successCount + result.failureCount}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { generateStoryboardVideo } from './src/tools/video/storyboard-video.js';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { mkdir } from 'fs/promises';
|
|
4
|
+
|
|
5
|
+
const API_KEY = process.env.GEMINI_API_KEY || 'AIzaSyDROqIj3wBKod1ks16MI9rwo7BZipqug9w';
|
|
6
|
+
const OUTPUT_DIR = './generated-assets/princess-emma';
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
console.log('\nš° Princess Emma - Short Test (2 scenes)\n');
|
|
10
|
+
|
|
11
|
+
await mkdir(OUTPUT_DIR, { recursive: true });
|
|
12
|
+
|
|
13
|
+
const result = await generateStoryboardVideo({
|
|
14
|
+
apiKey: API_KEY,
|
|
15
|
+
characterDescription: 'Princess Emma, a young girl with golden hair wearing a pink princess dress, and her fluffy white dog Fifi',
|
|
16
|
+
style: 'magical fairy tale animation, soft dreamy colors',
|
|
17
|
+
scenes: [
|
|
18
|
+
'A beautiful white castle with pink cone towers surrounded by lush green gardens and tiny glowing fairies',
|
|
19
|
+
'Princess Emma and her fluffy white dog Fifi walk out of the castle into the sunny garden'
|
|
20
|
+
],
|
|
21
|
+
aspectRatio: '16:9',
|
|
22
|
+
transition: 'crossfade',
|
|
23
|
+
transitionDuration: 0.5,
|
|
24
|
+
outputPath: join(OUTPUT_DIR, 'princess-emma-short-test.mp4'),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
console.log(`\n⨠Test complete!`);
|
|
28
|
+
console.log(`š Video: ${result.videoPath}`);
|
|
29
|
+
console.log(`ā±ļø Total time: ${(result.totalTime / 1000 / 60).toFixed(1)} minutes`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { generateStoryboardVideo } from './src/tools/video/storyboard-video.js';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { mkdir } from 'fs/promises';
|
|
4
|
+
|
|
5
|
+
const API_KEY = process.env.GEMINI_API_KEY || 'AIzaSyDROqIj3wBKod1ks16MI9rwo7BZipqug9w';
|
|
6
|
+
const OUTPUT_DIR = './generated-assets/princess-emma';
|
|
7
|
+
const REFERENCE_IMAGE = './generated-assets/princess-emma/IMG_3609.jpeg';
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
console.log('\nš° Princess Emma with Reference Image Test\n');
|
|
11
|
+
|
|
12
|
+
await mkdir(OUTPUT_DIR, { recursive: true });
|
|
13
|
+
|
|
14
|
+
const result = await generateStoryboardVideo({
|
|
15
|
+
apiKey: API_KEY,
|
|
16
|
+
characterDescription: 'Princess Emma, a young toddler girl with short blonde wavy hair and pink hair clips, wearing a pink princess dress, with her fluffy white small dog Fifi',
|
|
17
|
+
style: 'magical fairy tale animation, soft pastel colors, enchanted storybook illustration',
|
|
18
|
+
referenceImages: [REFERENCE_IMAGE],
|
|
19
|
+
scenes: [
|
|
20
|
+
'A beautiful white castle with bright pink cone-shaped roofs, surrounded by lush green gardens with colorful flowers and tiny glowing fairies flying around',
|
|
21
|
+
'Princess Emma and her fluffy white dog Fifi happily walk out of the castle gate into the sunny magical garden, birds singing'
|
|
22
|
+
],
|
|
23
|
+
aspectRatio: '16:9',
|
|
24
|
+
transition: 'crossfade',
|
|
25
|
+
transitionDuration: 0.5,
|
|
26
|
+
outputPath: join(OUTPUT_DIR, 'princess-emma-with-reference.mp4'),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
console.log(`\n⨠Test complete!`);
|
|
30
|
+
console.log(`š Video: ${result.videoPath}`);
|
|
31
|
+
console.log(`ā±ļø Total time: ${(result.totalTime / 1000 / 60).toFixed(1)} minutes`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { generateStoryboardVideo } from './src/tools/video/storyboard-video.js';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { mkdir } from 'fs/promises';
|
|
4
|
+
|
|
5
|
+
const API_KEY = process.env.GEMINI_API_KEY || 'AIzaSyDROqIj3wBKod1ks16MI9rwo7BZipqug9w';
|
|
6
|
+
const OUTPUT_DIR = './generated-assets/princess-emma';
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
console.log('\nš° Princess Emma and Fifi - Storyboard Video Test\n');
|
|
10
|
+
|
|
11
|
+
await mkdir(OUTPUT_DIR, { recursive: true });
|
|
12
|
+
|
|
13
|
+
const result = await generateStoryboardVideo({
|
|
14
|
+
apiKey: API_KEY,
|
|
15
|
+
characterDescription: 'Princess Emma, a young girl with golden hair wearing a pink princess dress, and her fluffy white dog Fifi',
|
|
16
|
+
style: 'magical fairy tale animation, soft dreamy colors, enchanted atmosphere',
|
|
17
|
+
scenes: [
|
|
18
|
+
'A beautiful white castle with pink cone towers surrounded by lush green gardens, flowers, and tiny glowing fairies flying around',
|
|
19
|
+
'Princess Emma and her fluffy white dog Fifi walk out of the castle door into the sunny garden, birds singing',
|
|
20
|
+
'A tiny blue fairy named Aqua with sparkling wings flies up to Princess Emma, looking worried and pointing at the tall grass',
|
|
21
|
+
'Princess Emma and Fifi search through the tall grass near a stone fountain, looking carefully for something',
|
|
22
|
+
'Fifi finds a glowing blue Aqua Stone near the fountain, barking happily while Princess Emma picks it up',
|
|
23
|
+
'Princess Emma hands the blue stone to the grateful fairy Aqua, everyone smiling with joy',
|
|
24
|
+
'The fairy waves her wand and all the flowers bloom, trees sparkle with magic, the garden transforms into a paradise'
|
|
25
|
+
],
|
|
26
|
+
aspectRatio: '16:9',
|
|
27
|
+
transition: 'crossfade',
|
|
28
|
+
transitionDuration: 0.8,
|
|
29
|
+
outputPath: join(OUTPUT_DIR, 'princess-emma-and-fifi.mp4'),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log(`\n⨠Story complete!`);
|
|
33
|
+
console.log(`š Video: ${result.videoPath}`);
|
|
34
|
+
console.log(`ā±ļø Total time: ${(result.totalTime / 1000 / 60).toFixed(1)} minutes`);
|
|
35
|
+
console.log(`š¬ Scenes: ${result.successCount}/${result.successCount + result.failureCount}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { GoogleGenAI } from '@google/genai';
|
|
2
|
+
import { createWriteStream } from 'fs';
|
|
3
|
+
import { Readable } from 'stream';
|
|
4
|
+
import * as fs from 'fs/promises';
|
|
5
|
+
|
|
6
|
+
async function generateProductAd() {
|
|
7
|
+
console.log('š¬ Generating Product Advertisement Video...\n');
|
|
8
|
+
|
|
9
|
+
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
|
|
10
|
+
|
|
11
|
+
const prompt = `Professional product advertisement video: An attractive woman in her 30s with long blonde-brown hair, wearing a grey tweed blazer over a white collared blouse, standing in a modern bright kitchen. She is holding a white cylindrical sports nutrition container with a grey lid, presenting it to the camera with a warm confident smile. She gestures towards the product while talking, professional lighting, clean modern kitchen with marble countertops in background, advertisement commercial style, high production value, 4K quality`;
|
|
12
|
+
|
|
13
|
+
console.log('š Prompt:', prompt);
|
|
14
|
+
console.log('\nā³ Starting video generation (30-90 seconds)...\n');
|
|
15
|
+
|
|
16
|
+
const startTime = Date.now();
|
|
17
|
+
|
|
18
|
+
let operation = await ai.models.generateVideos({
|
|
19
|
+
model: 'veo-2.0-generate-001',
|
|
20
|
+
prompt: prompt,
|
|
21
|
+
config: {
|
|
22
|
+
numberOfVideos: 1,
|
|
23
|
+
aspectRatio: '16:9',
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
console.log('š Operation started:', operation.name);
|
|
28
|
+
|
|
29
|
+
let pollCount = 0;
|
|
30
|
+
while (!operation.done) {
|
|
31
|
+
pollCount++;
|
|
32
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(0);
|
|
33
|
+
console.log(`ā³ Generating... (${elapsed}s elapsed, poll #${pollCount})`);
|
|
34
|
+
|
|
35
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
36
|
+
operation = await ai.operations.getVideosOperation({ operation });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
40
|
+
console.log(`\nā
Generation complete in ${duration}s`);
|
|
41
|
+
|
|
42
|
+
if (operation.response?.generatedVideos && operation.response.generatedVideos.length > 0) {
|
|
43
|
+
const video = operation.response.generatedVideos[0];
|
|
44
|
+
const videoUrl = `${video.video?.uri}&key=${process.env.GEMINI_API_KEY}`;
|
|
45
|
+
|
|
46
|
+
console.log('š„ Downloading video...');
|
|
47
|
+
|
|
48
|
+
await fs.mkdir('./generated-assets', { recursive: true });
|
|
49
|
+
const outputPath = './generated-assets/sponser-product-ad.mp4';
|
|
50
|
+
|
|
51
|
+
const resp = await fetch(videoUrl);
|
|
52
|
+
const writer = createWriteStream(outputPath);
|
|
53
|
+
Readable.fromWeb(resp.body as any).pipe(writer);
|
|
54
|
+
|
|
55
|
+
await new Promise(resolve => writer.on('finish', resolve));
|
|
56
|
+
|
|
57
|
+
console.log(`š¾ Saved to: ${outputPath}`);
|
|
58
|
+
console.log('\nš Done! Product ad video ready (no audio).');
|
|
59
|
+
console.log('\nā ļø Note: Product is generic (not exact Sponser branding)');
|
|
60
|
+
console.log('ā ļø Note: Person is AI-generated (similar style, not the actual person)');
|
|
61
|
+
} else {
|
|
62
|
+
console.log('ā No videos were generated');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
generateProductAd().catch(console.error);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { GeminiProvider } from './src/providers/gemini.js';
|
|
2
|
+
import { saveImage } from './src/utils/file-handler.js';
|
|
3
|
+
|
|
4
|
+
async function generateRalphDroid() {
|
|
5
|
+
console.log('š¤ Generating Ralph Wiggum in Cyberpunk Droid Suit...\n');
|
|
6
|
+
|
|
7
|
+
const provider = new GeminiProvider();
|
|
8
|
+
|
|
9
|
+
const prompt = 'A chubby cartoon character with a round face and buck teeth wearing a futuristic cyberpunk droid suit, neon purple and orange colors, mechanical armor with glowing circuits, retro-futuristic aesthetic, digital art style, detailed tech armor, cheerful expression';
|
|
10
|
+
|
|
11
|
+
console.log('š Prompt:', prompt);
|
|
12
|
+
console.log('ā³ Generating...\n');
|
|
13
|
+
|
|
14
|
+
const startTime = Date.now();
|
|
15
|
+
const images = await provider.generateImage(prompt, {
|
|
16
|
+
aspectRatio: '1:1',
|
|
17
|
+
count: 1
|
|
18
|
+
});
|
|
19
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
20
|
+
|
|
21
|
+
console.log(`ā
Generated in ${duration}s`);
|
|
22
|
+
console.log(`š¦ Size: ${(images[0].length / 1024).toFixed(1)}KB\n`);
|
|
23
|
+
|
|
24
|
+
const outputPath = await saveImage(images[0], './generated-assets', 'ralph-cyberpunk-droid', 0);
|
|
25
|
+
console.log(`š¾ Saved to: ${outputPath}`);
|
|
26
|
+
|
|
27
|
+
console.log('\nš Done! Your cyberpunk character is ready.');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
generateRalphDroid().catch(console.error);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { GeminiProvider } from './src/providers/gemini.js';
|
|
2
|
+
import { saveImage } from './src/utils/file-handler.js';
|
|
3
|
+
|
|
4
|
+
async function generateSocialPreview() {
|
|
5
|
+
console.log('š Generating Social Preview for Ralph Ultra...\n');
|
|
6
|
+
|
|
7
|
+
const provider = new GeminiProvider();
|
|
8
|
+
|
|
9
|
+
const projectName = "Ralph Ultra";
|
|
10
|
+
const description = "Production-grade autonomous AI agent orchestration with health monitoring, cost tracking, and auto-recovery";
|
|
11
|
+
const style = "bold";
|
|
12
|
+
|
|
13
|
+
const prompt = `Create a professional social media preview image (Open Graph) for a project called "${projectName}" with the description: "${description}".
|
|
14
|
+
|
|
15
|
+
Use a high-contrast design with vibrant colors.
|
|
16
|
+
Dark background (navy blue, deep purple, or black) with bright accent colors.
|
|
17
|
+
Display the project name "Ralph Ultra" in extra-bold, large white or yellow text.
|
|
18
|
+
Show the description in bright white text below.
|
|
19
|
+
Add dramatic lighting effects, glows, or neon-style elements.
|
|
20
|
+
Create a strong visual impact - this should grab attention immediately.
|
|
21
|
+
Bold, modern, energetic design.
|
|
22
|
+
|
|
23
|
+
Requirements:
|
|
24
|
+
- Image must be in landscape orientation (wider than tall) at approximately 1200x630 pixels ratio
|
|
25
|
+
- Text must be large and highly readable even at small sizes
|
|
26
|
+
- Composition should be centered and balanced
|
|
27
|
+
- Professional quality suitable for social media sharing (Facebook, Twitter, LinkedIn)
|
|
28
|
+
- Design should work well as a thumbnail preview`;
|
|
29
|
+
|
|
30
|
+
console.log(`š Project: ${projectName}`);
|
|
31
|
+
console.log(`š Description: ${description}`);
|
|
32
|
+
console.log(`šØ Style: ${style}`);
|
|
33
|
+
console.log('\nā³ Generating...\n');
|
|
34
|
+
|
|
35
|
+
const startTime = Date.now();
|
|
36
|
+
const images = await provider.generateImage(prompt, {
|
|
37
|
+
aspectRatio: '16:9',
|
|
38
|
+
count: 1
|
|
39
|
+
});
|
|
40
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
41
|
+
|
|
42
|
+
console.log(`ā
Generated in ${duration}s`);
|
|
43
|
+
console.log(`š¦ Size: ${(images[0].length / 1024).toFixed(1)}KB\n`);
|
|
44
|
+
|
|
45
|
+
const outputPath = await saveImage(images[0], './generated-assets', 'ralph-ultra-social-preview', 0);
|
|
46
|
+
console.log(`š¾ Saved to: ${outputPath}`);
|
|
47
|
+
|
|
48
|
+
console.log(`
|
|
49
|
+
š Add this to your HTML <head>:
|
|
50
|
+
\`\`\`html
|
|
51
|
+
<meta property="og:image" content="${outputPath}" />
|
|
52
|
+
<meta property="og:title" content="${projectName}" />
|
|
53
|
+
<meta property="og:description" content="${description}" />
|
|
54
|
+
<meta name="twitter:card" content="summary_large_image" />
|
|
55
|
+
\`\`\`
|
|
56
|
+
`);
|
|
57
|
+
|
|
58
|
+
console.log('š Done! Your social preview is ready for sharing.');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
generateSocialPreview().catch(console.error);
|