opencode-avatar 0.1.0 → 0.3.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.md +26 -8
- package/dist/electron.js +38 -8
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,22 +42,40 @@ Then add to your OpenCode config:
|
|
|
42
42
|
|
|
43
43
|
## Configuration
|
|
44
44
|
|
|
45
|
-
###
|
|
45
|
+
### API Key Configuration
|
|
46
46
|
|
|
47
|
-
Create a
|
|
47
|
+
Create a config file at `~/.config/opencode/opencode-avatar.json`:
|
|
48
48
|
|
|
49
|
-
```
|
|
50
|
-
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"falKey": "your_fal_ai_api_key_here"
|
|
52
|
+
}
|
|
51
53
|
```
|
|
52
54
|
|
|
53
55
|
Get your FAL.ai API key from [fal.ai](https://fal.ai).
|
|
54
56
|
|
|
55
57
|
### Avatar Images
|
|
56
58
|
|
|
57
|
-
The plugin
|
|
59
|
+
The plugin automatically downloads a default avatar (`avatar.png`) to `~/.config/opencode/avatar.png` if it doesn't exist. This serves as the source image for generating animated variants.
|
|
60
|
+
|
|
61
|
+
#### Using a Custom Avatar
|
|
62
|
+
|
|
63
|
+
To use your own custom avatar:
|
|
64
|
+
|
|
65
|
+
1. Place your custom `avatar.png` image in `~/.config/opencode/`
|
|
66
|
+
2. The plugin will use this as the base image for all avatar variants
|
|
67
|
+
3. Ensure your image has a solid green background (RGB: 0, 255, 0) for best results with the chroma key processing
|
|
68
|
+
|
|
69
|
+
#### Generated Variants
|
|
70
|
+
|
|
71
|
+
The plugin generates and caches avatar variants in the same directory:
|
|
72
|
+
|
|
73
|
+
- `avatar_write.png` - Writing pose
|
|
74
|
+
- `avatar_read.png` - Reading pose
|
|
75
|
+
- `avatar_thinking_hard.png` - Thinking animation
|
|
76
|
+
- And more based on tool usage
|
|
58
77
|
|
|
59
|
-
|
|
60
|
-
- `avatar.svg` - Fallback avatar (optional)
|
|
78
|
+
All avatars are stored in `~/.config/opencode/` for persistence across updates.
|
|
61
79
|
|
|
62
80
|
## How It Works
|
|
63
81
|
|
|
@@ -168,7 +186,7 @@ The output shows:
|
|
|
168
186
|
### Avatar Not Showing
|
|
169
187
|
|
|
170
188
|
1. Check console output for errors
|
|
171
|
-
2. Verify FAL.ai API key in
|
|
189
|
+
2. Verify FAL.ai API key in `~/.config/opencode/opencode-avatar.json`
|
|
172
190
|
3. Ensure port 47291 is available
|
|
173
191
|
4. Try restarting OpenCode
|
|
174
192
|
|
package/dist/electron.js
CHANGED
|
@@ -402,14 +402,29 @@ import { app, BrowserWindow, screen, Tray, Menu, nativeImage } from "electron";
|
|
|
402
402
|
import * as path from "path";
|
|
403
403
|
import * as http from "http";
|
|
404
404
|
import * as fs from "fs";
|
|
405
|
+
import * as os from "os";
|
|
405
406
|
import { fileURLToPath } from "url";
|
|
406
407
|
require_main().config();
|
|
408
|
+
function getFalKey() {
|
|
409
|
+
try {
|
|
410
|
+
const configPath = path.join(os.homedir(), ".config", "opencode", "opencode-avatar.json");
|
|
411
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
412
|
+
if (!config.falKey) {
|
|
413
|
+
console.warn("Warning: falKey not found in config file. Avatar generation will not work. Please set falKey in ~/.config/opencode/opencode-avatar.json");
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
return config.falKey;
|
|
417
|
+
} catch (error) {
|
|
418
|
+
console.warn(`Warning: Failed to read config file: ${error.message}. Avatar generation will not work. Please ensure ~/.config/opencode/opencode-avatar.json exists and contains falKey.`);
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
407
422
|
var FAL_CDN_URL = "https://v3.fal.media";
|
|
408
423
|
var FAL_REST_URL = "https://rest.alpha.fal.ai";
|
|
409
424
|
var FAL_NANO_BANANA_URL = "https://fal.run/fal-ai/nano-banana-pro/edit";
|
|
410
425
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
411
426
|
var __dirnameResolved = path.dirname(__filename2);
|
|
412
|
-
var AVATAR_DIR = path.join(
|
|
427
|
+
var AVATAR_DIR = path.join(os.homedir(), ".config", "opencode");
|
|
413
428
|
var HTML_CONTENT = `<!DOCTYPE html>
|
|
414
429
|
<html>
|
|
415
430
|
<head>
|
|
@@ -551,13 +566,13 @@ function getAvatarPath() {
|
|
|
551
566
|
}
|
|
552
567
|
return path.join(AVATAR_DIR, "avatar.png");
|
|
553
568
|
}
|
|
554
|
-
async function uploadFile(filePath) {
|
|
569
|
+
async function uploadFile(filePath, falKey) {
|
|
555
570
|
const fileBuffer = fs.readFileSync(filePath);
|
|
556
571
|
const fileName = path.basename(filePath);
|
|
557
572
|
const tokenResponse = await fetch(`${FAL_REST_URL}/storage/auth/token?storage_type=fal-cdn-v3`, {
|
|
558
573
|
method: "POST",
|
|
559
574
|
headers: {
|
|
560
|
-
Authorization: `Key ${
|
|
575
|
+
Authorization: `Key ${falKey}`,
|
|
561
576
|
Accept: "application/json",
|
|
562
577
|
"Content-Type": "application/json"
|
|
563
578
|
},
|
|
@@ -585,11 +600,11 @@ async function uploadFile(filePath) {
|
|
|
585
600
|
const result = await response.json();
|
|
586
601
|
return result.access_url || result.url || "";
|
|
587
602
|
}
|
|
588
|
-
async function generateAvatarImage(imageUrl, prompt) {
|
|
603
|
+
async function generateAvatarImage(imageUrl, prompt, falKey) {
|
|
589
604
|
const response = await fetch(FAL_NANO_BANANA_URL, {
|
|
590
605
|
method: "POST",
|
|
591
606
|
headers: {
|
|
592
|
-
Authorization: `Key ${
|
|
607
|
+
Authorization: `Key ${falKey}`,
|
|
593
608
|
"Content-Type": "application/json"
|
|
594
609
|
},
|
|
595
610
|
body: JSON.stringify({
|
|
@@ -612,6 +627,11 @@ async function downloadImage(url, outputPath) {
|
|
|
612
627
|
fs.writeFileSync(outputPath, buffer);
|
|
613
628
|
}
|
|
614
629
|
async function generateAvatarForPrompt(prompt) {
|
|
630
|
+
const falKey = getFalKey();
|
|
631
|
+
if (!falKey) {
|
|
632
|
+
console.warn("falKey is not set. Cannot generate avatar. Using default avatar.");
|
|
633
|
+
return path.join(AVATAR_DIR, "avatar.png");
|
|
634
|
+
}
|
|
615
635
|
const cachedFilename = promptToFilename(prompt);
|
|
616
636
|
const cachedPath = path.join(AVATAR_DIR, cachedFilename);
|
|
617
637
|
if (fs.existsSync(cachedPath)) {
|
|
@@ -627,9 +647,9 @@ async function generateAvatarForPrompt(prompt) {
|
|
|
627
647
|
return;
|
|
628
648
|
}
|
|
629
649
|
const sourceAvatar = path.join(AVATAR_DIR, "avatar.png");
|
|
630
|
-
const uploadedUrl = await uploadFile(sourceAvatar);
|
|
650
|
+
const uploadedUrl = await uploadFile(sourceAvatar, falKey);
|
|
631
651
|
const fullPrompt = `make a character variant: ${prompt}. Keep the background as a solid green screen color. Do not let the green screen color appear in reflections or on the subject.`;
|
|
632
|
-
const result = await generateAvatarImage(uploadedUrl, fullPrompt);
|
|
652
|
+
const result = await generateAvatarImage(uploadedUrl, fullPrompt, falKey);
|
|
633
653
|
const outputUrl = result.images?.[0]?.url || result.image?.url || result.url;
|
|
634
654
|
if (!outputUrl) {
|
|
635
655
|
throw new Error("No output image URL in response: " + JSON.stringify(result, null, 2));
|
|
@@ -814,7 +834,17 @@ function createTray() {
|
|
|
814
834
|
tray.on("click", () => mainWindow?.isVisible() ? mainWindow.hide() : mainWindow?.show());
|
|
815
835
|
}
|
|
816
836
|
app.commandLine.appendSwitch("enable-transparent-visuals");
|
|
817
|
-
app.whenReady().then(() => {
|
|
837
|
+
app.whenReady().then(async () => {
|
|
838
|
+
fs.mkdirSync(AVATAR_DIR, { recursive: true });
|
|
839
|
+
const avatarPath = path.join(AVATAR_DIR, "avatar.png");
|
|
840
|
+
if (!fs.existsSync(avatarPath)) {
|
|
841
|
+
try {
|
|
842
|
+
await downloadImage("https://richardanaya.github.io/opencode-avatar/avatar.png", avatarPath);
|
|
843
|
+
console.log("Downloaded default avatar to", avatarPath);
|
|
844
|
+
} catch (error) {
|
|
845
|
+
console.warn("Failed to download default avatar:", error);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
818
848
|
setTimeout(() => {
|
|
819
849
|
createWindow();
|
|
820
850
|
createTray();
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { spawn } from "child_process";
|
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
import * as http from "http";
|
|
6
6
|
import * as fs from "fs";
|
|
7
|
-
var __dirname = "/var/home/wizard/
|
|
7
|
+
var __dirname = "/var/home/wizard/av";
|
|
8
8
|
var AVATAR_DIR = __dirname;
|
|
9
9
|
var DEFAULT_AVATAR = "avatar.png";
|
|
10
10
|
var THINKING_PROMPT = "thinking hard";
|