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 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 getFalKey() {
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 falKey = getFalKey();
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
- 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.`;
652
- const result = await generateAvatarImage(uploadedUrl, fullPrompt, falKey);
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
- setAvatarViaHttp(prompt, toolName);
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
- await requestAvatarGeneration(THINKING_PROMPT);
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-avatar",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Dynamic desktop avatar plugin for OpenCode that reacts to your coding activities",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",