vibemon 1.9.3 → 1.10.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
@@ -20,11 +20,17 @@ The app launches in the system tray and listens on `http://127.0.0.1:19280`.
20
20
 
21
21
  ## Supported Tools
22
22
 
23
- - **Apto** - Personal AI coding assistant
24
23
  - **[Claude Code](https://claude.ai/code)** - Anthropic's AI coding assistant
24
+ - **[Codex](https://openai.com/codex)** - OpenAI's AI coding agent
25
25
  - **[Kiro](https://kiro.dev/)** - AWS's AI coding assistant
26
26
  - **[OpenClaw](https://openclaw.ai/)** - Open-source computer use agent
27
27
 
28
+ ## Integration Notes
29
+
30
+ - **Claude Code** and **Kiro** are the cleanest real-time integrations because they expose direct hook events around prompts, tool use, and turn completion.
31
+ - **Codex** is supported, but its interactive hook surface is currently narrower than Claude Code. For automation, `codex exec --json` provides richer telemetry.
32
+ - **OpenClaw** support is plugin-based. VibeMon uses a bridge plugin because OpenClaw's simpler internal hooks are not designed around the same tool loop.
33
+
28
34
  ## Features
29
35
 
30
36
  - **Frameless Window** - Clean floating design
Binary file
@@ -72,6 +72,18 @@ function createTrayIcon(state, character = 'clawd') {
72
72
  rect(13, 16, 3, 3, charColor); // Right leg
73
73
  rect(7, 10, 2, 2, '#40E0D0'); // Left eye (cyan)
74
74
  rect(13, 10, 2, 2, '#40E0D0'); // Right eye (cyan)
75
+ } else if (charName === 'codex') {
76
+ // Draw codex character (green terminal robot)
77
+ rect(8, 2, 6, 2, charColor); // Top cap
78
+ rect(6, 4, 10, 2, charColor); // Head taper
79
+ rect(5, 6, 12, 9, charColor); // Main body
80
+ rect(3, 9, 2, 5, charColor); // Left arm
81
+ rect(17, 9, 2, 5, charColor); // Right arm
82
+ rect(7, 15, 3, 4, charColor); // Left leg
83
+ rect(12, 15, 3, 4, charColor); // Right leg
84
+ rect(7, 9, 2, 2, COLOR_EYE); // Left eye
85
+ rect(12, 9, 2, 2, COLOR_EYE); // Right eye
86
+ rect(9, 12, 4, 1, COLOR_EYE); // Mouth
75
87
  } else {
76
88
  // Draw clawd character (default)
77
89
  rect(4, 6, 14, 8, charColor); // Body
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibemon",
3
- "version": "1.9.3",
3
+ "version": "1.10.1",
4
4
  "description": "AI assistant status monitor",
5
5
  "main": "main.js",
6
6
  "bin": {
package/renderer.js CHANGED
@@ -24,10 +24,10 @@ async function init() {
24
24
  vibeMonEngine = createVibeMonEngine(container, {
25
25
  useEmoji,
26
26
  characterImageUrls: {
27
- apto: `${STATIC_BASE}/characters/apto.png`,
28
27
  clawd: `${STATIC_BASE}/characters/clawd.png`,
29
28
  kiro: `${STATIC_BASE}/characters/kiro.png`,
30
- claw: `${STATIC_BASE}/characters/claw.png`
29
+ claw: `${STATIC_BASE}/characters/claw.png`,
30
+ codex: `${STATIC_BASE}/characters/codex.png`
31
31
  }
32
32
  });
33
33
  await vibeMonEngine.init();
@@ -63,13 +63,13 @@
63
63
 
64
64
  "VALID_STATES": ["start", "idle", "thinking", "planning", "working", "packing", "notification", "done", "sleep", "alert"],
65
65
 
66
- "CHARACTER_NAMES": ["apto", "clawd", "kiro", "claw"],
66
+ "CHARACTER_NAMES": ["clawd", "kiro", "claw", "codex"],
67
67
 
68
68
  "CHARACTER_COLORS": {
69
- "apto": "#797C98",
70
69
  "clawd": "#D97757",
71
70
  "kiro": "#FFFFFF",
72
- "claw": "#DD4444"
71
+ "claw": "#DD4444",
72
+ "codex": "#10A37F"
73
73
  },
74
74
 
75
75
  "STATE_COLORS": {
package/stats.html CHANGED
@@ -306,11 +306,62 @@
306
306
  return `${minutes}m`;
307
307
  };
308
308
 
309
- const getModelShortName = (model) => {
310
- if (model.includes('opus')) return 'Opus 4.5';
311
- if (model.includes('sonnet')) return 'Sonnet 4.5';
312
- if (model.includes('haiku')) return 'Haiku';
313
- return model.split('-').slice(1, 3).join(' ');
309
+ const titleCase = (value) => value
310
+ .split(/[\s_-]+/)
311
+ .filter(Boolean)
312
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1))
313
+ .join(' ');
314
+
315
+ const getNumericVersion = (parts, startIndex) => {
316
+ const versionParts = [];
317
+
318
+ for (let i = startIndex; i < parts.length; i++) {
319
+ const part = parts[i];
320
+ if (!/^\d+$/.test(part)) break;
321
+ // Ignore trailing release-date style suffixes like 20251101.
322
+ if (part.length >= 6) break;
323
+ versionParts.push(part);
324
+ if (versionParts.length === 2) break;
325
+ }
326
+
327
+ return versionParts.join('.');
328
+ };
329
+
330
+ const getModelInfo = (model) => {
331
+ if (!model || typeof model !== 'string') {
332
+ return { key: 'unknown', name: 'Unknown' };
333
+ }
334
+
335
+ const parts = model.toLowerCase().split('-').filter(Boolean);
336
+ if (parts.length === 0) {
337
+ return { key: model, name: model };
338
+ }
339
+
340
+ const provider = parts[0];
341
+ const family = parts[1] || 'unknown';
342
+ const version = getNumericVersion(parts, 2);
343
+ const familyName = titleCase(family);
344
+ const name = version ? `${familyName} ${version}` : familyName;
345
+ const key = [provider, family, version].filter(Boolean).join(':');
346
+
347
+ return { key, name };
348
+ };
349
+
350
+ const aggregateModelUsage = (modelUsage) => {
351
+ return Object.entries(modelUsage || {}).reduce((acc, [model, usage]) => {
352
+ const info = getModelInfo(model);
353
+ if (!acc[info.key]) {
354
+ acc[info.key] = { name: info.name };
355
+ }
356
+
357
+ Object.entries(usage || {}).forEach(([metric, value]) => {
358
+ if (typeof value === 'number' && Number.isFinite(value)) {
359
+ acc[info.key][metric] = (acc[info.key][metric] || 0) + value;
360
+ }
361
+ });
362
+
363
+ return acc;
364
+ }, {});
314
365
  };
315
366
 
316
367
  async function loadStats() {
@@ -502,13 +553,18 @@
502
553
 
503
554
  function renderModelList(modelUsage) {
504
555
  const container = document.getElementById('modelList');
505
- const entries = Object.entries(modelUsage);
506
-
507
- container.innerHTML = entries.map(([model, usage]) => {
556
+ const entries = Object.values(aggregateModelUsage(modelUsage))
557
+ .sort((a, b) => {
558
+ const totalA = (a.inputTokens || 0) + (a.outputTokens || 0);
559
+ const totalB = (b.inputTokens || 0) + (b.outputTokens || 0);
560
+ return totalB - totalA;
561
+ });
562
+
563
+ container.innerHTML = entries.map((usage) => {
508
564
  const total = (usage.inputTokens || 0) + (usage.outputTokens || 0);
509
565
  return `
510
566
  <div class="model-item">
511
- <span class="model-name">${getModelShortName(model)}</span>
567
+ <span class="model-name">${usage.name}</span>
512
568
  <span class="model-tokens">${formatNumber(total)} tokens</span>
513
569
  </div>
514
570
  `;
Binary file