opencode-avatar 0.3.0 → 0.3.1
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 +14 -0
- package/dist/electron.js +12 -9
- package/dist/index.js +11 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ A dynamic desktop avatar plugin for OpenCode that displays animated character re
|
|
|
13
13
|
- **Non-Intrusive**: Appears without stealing focus, stays on top
|
|
14
14
|
- **Auto-Shutdown**: Automatically closes when OpenCode exits
|
|
15
15
|
- **Toast Notifications**: Shows progress for avatar generation
|
|
16
|
+
- **Customizable Prompts**: Optional prompt configuration for personalized avatar styles
|
|
16
17
|
|
|
17
18
|
## Installation
|
|
18
19
|
|
|
@@ -54,6 +55,19 @@ Create a config file at `~/.config/opencode/opencode-avatar.json`:
|
|
|
54
55
|
|
|
55
56
|
Get your FAL.ai API key from [fal.ai](https://fal.ai).
|
|
56
57
|
|
|
58
|
+
### Custom Prompt Configuration (Optional)
|
|
59
|
+
|
|
60
|
+
You can optionally add a `"prompt"` field to customize how avatars are generated. This text will be appended to all avatar generation prompts:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"falKey": "your_fal_ai_api_key_here",
|
|
65
|
+
"prompt": "in a futuristic cyberpunk style with neon lights"
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The prompt will be added to the end of the AI generation request, allowing you to customize the avatar style, theme, or appearance consistently across all avatar variants.
|
|
70
|
+
|
|
57
71
|
### Avatar Images
|
|
58
72
|
|
|
59
73
|
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.
|
package/dist/electron.js
CHANGED
|
@@ -405,18 +405,18 @@ import * as fs from "fs";
|
|
|
405
405
|
import * as os from "os";
|
|
406
406
|
import { fileURLToPath } from "url";
|
|
407
407
|
require_main().config();
|
|
408
|
-
function
|
|
408
|
+
function getConfig() {
|
|
409
409
|
try {
|
|
410
410
|
const configPath = path.join(os.homedir(), ".config", "opencode", "opencode-avatar.json");
|
|
411
411
|
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
412
412
|
if (!config.falKey) {
|
|
413
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;
|
|
414
|
+
return { falKey: null, prompt: null };
|
|
415
415
|
}
|
|
416
|
-
return config.falKey;
|
|
416
|
+
return { falKey: config.falKey, prompt: config.prompt || null };
|
|
417
417
|
} catch (error) {
|
|
418
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;
|
|
419
|
+
return { falKey: null, prompt: null };
|
|
420
420
|
}
|
|
421
421
|
}
|
|
422
422
|
var FAL_CDN_URL = "https://v3.fal.media";
|
|
@@ -627,8 +627,8 @@ async function downloadImage(url, outputPath) {
|
|
|
627
627
|
fs.writeFileSync(outputPath, buffer);
|
|
628
628
|
}
|
|
629
629
|
async function generateAvatarForPrompt(prompt) {
|
|
630
|
-
const
|
|
631
|
-
if (!falKey) {
|
|
630
|
+
const config = getConfig();
|
|
631
|
+
if (!config.falKey) {
|
|
632
632
|
console.warn("falKey is not set. Cannot generate avatar. Using default avatar.");
|
|
633
633
|
return path.join(AVATAR_DIR, "avatar.png");
|
|
634
634
|
}
|
|
@@ -647,9 +647,12 @@ async function generateAvatarForPrompt(prompt) {
|
|
|
647
647
|
return;
|
|
648
648
|
}
|
|
649
649
|
const sourceAvatar = path.join(AVATAR_DIR, "avatar.png");
|
|
650
|
-
const uploadedUrl = await uploadFile(sourceAvatar, falKey);
|
|
651
|
-
|
|
652
|
-
|
|
650
|
+
const uploadedUrl = await uploadFile(sourceAvatar, config.falKey);
|
|
651
|
+
let 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.`;
|
|
652
|
+
if (config.prompt) {
|
|
653
|
+
fullPrompt += ` ${config.prompt}`;
|
|
654
|
+
}
|
|
655
|
+
const result = await generateAvatarImage(uploadedUrl, fullPrompt, config.falKey);
|
|
653
656
|
const outputUrl = result.images?.[0]?.url || result.image?.url || result.url;
|
|
654
657
|
if (!outputUrl) {
|
|
655
658
|
throw new Error("No output image URL in response: " + JSON.stringify(result, null, 2));
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ var currentAvatar = DEFAULT_AVATAR;
|
|
|
21
21
|
var isThinking = false;
|
|
22
22
|
var isToolActive = false;
|
|
23
23
|
var isShuttingDown = false;
|
|
24
|
+
var idleTriggered = false;
|
|
24
25
|
var heartbeatInterval = null;
|
|
25
26
|
function sendHeartbeat() {
|
|
26
27
|
const req = http.request({
|
|
@@ -229,12 +230,15 @@ var AvatarPlugin = async ({ client }) => {
|
|
|
229
230
|
res.on("end", () => {
|
|
230
231
|
if (!showToasts) {
|
|
231
232
|
isToolActive = false;
|
|
233
|
+
isThinking = false;
|
|
232
234
|
}
|
|
233
235
|
if (res.statusCode === 200) {
|
|
234
236
|
if (showToasts) {
|
|
235
237
|
showInfoToast(`Avatar ready: ${prompt}`);
|
|
236
238
|
}
|
|
237
|
-
|
|
239
|
+
if (!idleTriggered || showToasts) {
|
|
240
|
+
setAvatarViaHttp(prompt, toolName);
|
|
241
|
+
}
|
|
238
242
|
resolve();
|
|
239
243
|
} else {
|
|
240
244
|
if (showToasts) {
|
|
@@ -268,14 +272,18 @@ var AvatarPlugin = async ({ client }) => {
|
|
|
268
272
|
const userMessage = output.parts.find((part) => part.type === "text" && part.messageID === input.messageID);
|
|
269
273
|
if (userMessage?.text) {}
|
|
270
274
|
if (userMessage?.text && !isThinking) {
|
|
275
|
+
idleTriggered = false;
|
|
271
276
|
isThinking = true;
|
|
272
|
-
|
|
277
|
+
requestAvatarGeneration(THINKING_PROMPT, false).catch(() => {
|
|
278
|
+
isThinking = false;
|
|
279
|
+
});
|
|
273
280
|
}
|
|
274
281
|
},
|
|
275
282
|
"tool.execute.before": async (input) => {
|
|
276
283
|
const toolName = input.tool;
|
|
277
284
|
const toolDescription = getToolDescription(toolName);
|
|
278
285
|
const prompt = getToolPrompt(toolName, toolDescription);
|
|
286
|
+
idleTriggered = false;
|
|
279
287
|
isToolActive = true;
|
|
280
288
|
requestAvatarGeneration(prompt, false, toolName).catch((err) => {
|
|
281
289
|
isToolActive = false;
|
|
@@ -283,6 +291,7 @@ var AvatarPlugin = async ({ client }) => {
|
|
|
283
291
|
},
|
|
284
292
|
event: async ({ event }) => {
|
|
285
293
|
if (event.type === "session.idle" && (isThinking || isToolActive)) {
|
|
294
|
+
idleTriggered = true;
|
|
286
295
|
isThinking = false;
|
|
287
296
|
isToolActive = false;
|
|
288
297
|
await setAvatarViaHttp();
|