opencode-avatar 0.3.10 → 0.3.12

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/dist/electron.js CHANGED
@@ -537,8 +537,10 @@ var httpServer = null;
537
537
  var ongoingGenerations = new Map;
538
538
  var lastHeartbeat = Date.now();
539
539
  var HEARTBEAT_TIMEOUT = 1000;
540
- function promptToFilename(prompt) {
541
- return "avatar_" + prompt.toLowerCase().replace(/[^a-z0-9\s]/g, "").replace(/\s+/g, "_").substring(0, 50) + ".png";
540
+ function promptToFilename(prompt, agentBase) {
541
+ const base = agentBase || "avatar";
542
+ const action = prompt.toLowerCase().replace(/[^a-z0-9\s]/g, "").replace(/\s+/g, "_").substring(0, 50);
543
+ return `${base}-${action}.png`;
542
544
  }
543
545
  function getAvatarPort() {
544
546
  const args = process.argv;
@@ -620,13 +622,14 @@ async function downloadImage(url, outputPath) {
620
622
  const buffer = Buffer.from(await response.arrayBuffer());
621
623
  fs.writeFileSync(outputPath, buffer);
622
624
  }
623
- async function generateAvatarForPrompt(prompt) {
625
+ async function generateAvatarForPrompt(prompt, agentBase) {
624
626
  const config = getConfig();
627
+ const base = agentBase || "avatar";
625
628
  if (!config.falKey) {
626
629
  console.warn("falKey is not set. Cannot generate avatar. Using default avatar.");
627
- return path.join(AVATAR_DIR, "avatar.png");
630
+ return path.join(AVATAR_DIR, `${base}.png`);
628
631
  }
629
- const cachedFilename = promptToFilename(prompt);
632
+ const cachedFilename = promptToFilename(prompt, base);
630
633
  const cachedPath = path.join(AVATAR_DIR, cachedFilename);
631
634
  if (fs.existsSync(cachedPath)) {
632
635
  return cachedPath;
@@ -640,9 +643,12 @@ async function generateAvatarForPrompt(prompt) {
640
643
  if (fs.existsSync(cachedPath)) {
641
644
  return;
642
645
  }
643
- const sourceAvatar = path.join(AVATAR_DIR, "avatar.png");
646
+ let sourceAvatar = path.join(AVATAR_DIR, `${base}.png`);
647
+ if (!fs.existsSync(sourceAvatar)) {
648
+ sourceAvatar = path.join(AVATAR_DIR, "avatar.png");
649
+ }
644
650
  const uploadedUrl = await uploadFile(sourceAvatar, config.falKey);
645
- let fullPrompt = `make a character variant: ${prompt}, the references for the character variant will be of command line utility tools. Keep the background as a solid green screen color. Do not let the green screen color appear in reflections or on the subject.`;
651
+ let fullPrompt = `make a character variant: ${prompt}. The action should be literal - the character should actually be performing the action (writing means writing, typing means typing, etc.), not shown as some abstract Terminal visualization or text output. Maintain the character's original essence and physicality. Keep the background as a solid green screen color. Do not let the green screen color appear in reflections or on the subject.`;
646
652
  if (config.prompt) {
647
653
  fullPrompt += ` ${config.prompt}`;
648
654
  }
@@ -702,13 +708,13 @@ function startAvatarServer() {
702
708
  });
703
709
  req.on("end", async () => {
704
710
  try {
705
- const { prompt } = JSON.parse(body);
711
+ const { prompt, agentBase } = JSON.parse(body);
706
712
  if (!prompt) {
707
713
  res.writeHead(400, { "Content-Type": "application/json" });
708
714
  res.end(JSON.stringify({ error: "Missing prompt" }));
709
715
  return;
710
716
  }
711
- const avatarPath = await generateAvatarForPrompt(prompt);
717
+ const avatarPath = await generateAvatarForPrompt(prompt, agentBase);
712
718
  if (mainWindow) {
713
719
  const imageBuffer = fs.readFileSync(avatarPath);
714
720
  const base64 = imageBuffer.toString("base64");
package/dist/index.js CHANGED
@@ -11,6 +11,19 @@ var AVATAR_DIR = path.join(os.homedir(), ".config", "opencode");
11
11
  var DEFAULT_AVATAR = "avatar.png";
12
12
  var THINKING_PROMPT = "thinking hard";
13
13
  var AVATAR_PORT = 47291;
14
+ function normalizeAgentName(name) {
15
+ return name.toLowerCase().replace(/\s+/g, "_");
16
+ }
17
+ function getAgentAvatarBase(agentName) {
18
+ if (!agentName)
19
+ return "avatar";
20
+ const normalized = normalizeAgentName(agentName);
21
+ const agentAvatarPath = path.join(AVATAR_DIR, `avatar-${normalized}.png`);
22
+ if (fs.existsSync(agentAvatarPath)) {
23
+ return `avatar-${normalized}`;
24
+ }
25
+ return "avatar";
26
+ }
14
27
  function getToolPrompt(toolName, toolDescription) {
15
28
  if (toolDescription) {
16
29
  const shortDesc = toolDescription.split(".")[0].substring(0, 50);
@@ -26,6 +39,8 @@ var isShuttingDown = false;
26
39
  var idleTriggered = false;
27
40
  var currentRequestId = null;
28
41
  var heartbeatInterval = null;
42
+ var currentAgentName = null;
43
+ var currentAgentBase = "avatar";
29
44
  function sendHeartbeat() {
30
45
  const req = http.request({
31
46
  hostname: "localhost",
@@ -88,21 +103,30 @@ async function sendShutdownCommand() {
88
103
  req.end();
89
104
  });
90
105
  }
91
- function promptToFilename(prompt, toolName) {
106
+ function promptToFilename(prompt, toolName, agentBase) {
107
+ const base = agentBase || "avatar";
92
108
  const baseName = toolName || prompt;
93
- return "avatar_" + baseName.toLowerCase().replace(/[^a-z0-9\s]/g, "").replace(/\s+/g, "_").substring(0, 50) + ".png";
109
+ const action = baseName.toLowerCase().replace(/[^a-z0-9\s]/g, "").replace(/\s+/g, "_").substring(0, 50);
110
+ return `${base}-${action}.png`;
94
111
  }
95
- function getAvatarPath(prompt, toolName) {
96
- const defaultAvatar = path.join(AVATAR_DIR, DEFAULT_AVATAR);
112
+ function getAvatarPath(prompt, toolName, agentBase) {
113
+ const base = agentBase || currentAgentBase;
114
+ const defaultAvatar = path.join(AVATAR_DIR, `${base}.png`);
97
115
  if (!prompt) {
116
+ if (!fs.existsSync(defaultAvatar)) {
117
+ return path.join(AVATAR_DIR, DEFAULT_AVATAR);
118
+ }
98
119
  return defaultAvatar;
99
120
  }
100
- const filename = promptToFilename(prompt, toolName);
121
+ const filename = promptToFilename(prompt, toolName, base);
101
122
  const avatarPath = path.join(AVATAR_DIR, filename);
102
123
  if (fs.existsSync(avatarPath)) {
103
124
  return avatarPath;
104
125
  }
105
- return defaultAvatar;
126
+ if (fs.existsSync(defaultAvatar)) {
127
+ return defaultAvatar;
128
+ }
129
+ return path.join(AVATAR_DIR, DEFAULT_AVATAR);
106
130
  }
107
131
  async function startElectron(avatarPath) {
108
132
  if (isShuttingDown) {
@@ -256,7 +280,7 @@ var AvatarPlugin = async ({ client }) => {
256
280
  }
257
281
  reject(err);
258
282
  });
259
- req.write(JSON.stringify({ prompt }));
283
+ req.write(JSON.stringify({ prompt, agentBase: currentAgentBase }));
260
284
  req.end();
261
285
  });
262
286
  }
@@ -292,6 +316,16 @@ var AvatarPlugin = async ({ client }) => {
292
316
  });
293
317
  },
294
318
  event: async ({ event }) => {
319
+ if (event.type === "message.updated") {
320
+ const message = event.properties?.info;
321
+ if (message?.role === "user" && message?.agent) {
322
+ const agentName = message.agent;
323
+ if (currentAgentName !== agentName) {
324
+ currentAgentName = agentName;
325
+ currentAgentBase = getAgentAvatarBase(currentAgentName);
326
+ }
327
+ }
328
+ }
295
329
  if (event.type === "session.idle" && (isThinking || isToolActive)) {
296
330
  idleTriggered = true;
297
331
  isThinking = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-avatar",
3
- "version": "0.3.10",
3
+ "version": "0.3.12",
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",