chatccc 0.2.193 → 0.2.194
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/package.json +1 -1
- package/src/__tests__/feishu-avatar.test.ts +40 -6
- package/src/feishu-api.ts +35 -31
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
1
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
|
|
@@ -68,7 +68,7 @@ function mockAvatarFetch(uploadedNames: string[], usageResponse: Response): void
|
|
|
68
68
|
}));
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
function mockAvatarUploadOnlyFetch(uploadedNames: string[]): ReturnType<typeof vi.fn> {
|
|
71
|
+
function mockAvatarUploadOnlyFetch(uploadedNames: string[]): ReturnType<typeof vi.fn> {
|
|
72
72
|
const fetchMock = vi.fn(async (url: string | URL | Request, init?: RequestInit) => {
|
|
73
73
|
const urlText = String(url);
|
|
74
74
|
if (urlText === "https://open.feishu.test/im/v1/images") {
|
|
@@ -83,10 +83,44 @@ function mockAvatarUploadOnlyFetch(uploadedNames: string[]): ReturnType<typeof v
|
|
|
83
83
|
throw new Error(`unexpected fetch: ${urlText}`);
|
|
84
84
|
});
|
|
85
85
|
vi.stubGlobal("fetch", fetchMock);
|
|
86
|
-
return fetchMock;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
describe("
|
|
86
|
+
return fetchMock;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
describe("Plain avatar fallback", () => {
|
|
90
|
+
afterEach(() => {
|
|
91
|
+
vi.unstubAllGlobals();
|
|
92
|
+
vi.doUnmock("node:os");
|
|
93
|
+
vi.doUnmock("../config.ts");
|
|
94
|
+
vi.doUnmock("../cursor-usage.ts");
|
|
95
|
+
vi.restoreAllMocks();
|
|
96
|
+
getCursorUsageSummaryMock.mockReset();
|
|
97
|
+
mockConfig.cursor.avatarBatteryMode = "apiPercent";
|
|
98
|
+
mockConfig.cursor.onDemandMonthlyBudget = 1000;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("uses a status-only avatar for unknown tools instead of falling back to Claude", async () => {
|
|
102
|
+
const homeDir = await mkdtemp(join(tmpdir(), "chatccc-avatar-home-"));
|
|
103
|
+
const userDataDir = await mkdtemp(join(tmpdir(), "chatccc-avatar-data-"));
|
|
104
|
+
const uploadedNames: string[] = [];
|
|
105
|
+
mockAvatarUploadOnlyFetch(uploadedNames);
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const { setChatAvatar } = await loadFeishuApiWithHome(homeDir, userDataDir);
|
|
109
|
+
await setChatAvatar("tenant-token", "chat_1", "ccc", "new");
|
|
110
|
+
|
|
111
|
+
expect(uploadedNames).toEqual(["avatar_plain_new.jpg"]);
|
|
112
|
+
const cacheRaw = await readFile(join(userDataDir, "state", "avatar-image-keys.json"), "utf-8");
|
|
113
|
+
const cache = JSON.parse(cacheRaw) as Record<string, string>;
|
|
114
|
+
expect(cache["plain:new"]).toBe("img_test");
|
|
115
|
+
expect(cache["claude:new"]).toBeUndefined();
|
|
116
|
+
} finally {
|
|
117
|
+
await rm(homeDir, { recursive: true, force: true });
|
|
118
|
+
await rm(userDataDir, { recursive: true, force: true });
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("Codex avatar usage battery", () => {
|
|
90
124
|
afterEach(() => {
|
|
91
125
|
vi.unstubAllGlobals();
|
|
92
126
|
vi.doUnmock("node:os");
|
package/src/feishu-api.ts
CHANGED
|
@@ -319,11 +319,12 @@ const AVATAR_BADGES: Record<string, string> = {
|
|
|
319
319
|
cursor: resolvePath(AVATAR_BADGE_DIR, "badge_cursor.png"),
|
|
320
320
|
codex: resolvePath(AVATAR_BADGE_DIR, "badge_codex.png"),
|
|
321
321
|
};
|
|
322
|
-
const AVATAR_SIZE = 256;
|
|
323
|
-
const AVATAR_BADGE_SIZE = 92;
|
|
324
|
-
const AVATAR_BADGE_MARGIN = 10;
|
|
325
|
-
const
|
|
326
|
-
const
|
|
322
|
+
const AVATAR_SIZE = 256;
|
|
323
|
+
const AVATAR_BADGE_SIZE = 92;
|
|
324
|
+
const AVATAR_BADGE_MARGIN = 10;
|
|
325
|
+
const PLAIN_AVATAR_TOOL = "plain";
|
|
326
|
+
const CODEX_AVATAR_USAGE_STYLE_VERSION = "usage-ring-gray-consumed-v13";
|
|
327
|
+
const CURSOR_AVATAR_USAGE_STYLE_VERSION = "usage-battery-v1";
|
|
327
328
|
|
|
328
329
|
export interface CodexUsageBalance {
|
|
329
330
|
usedPercent: number;
|
|
@@ -354,17 +355,17 @@ export interface CodexResetConsumeResult {
|
|
|
354
355
|
const avatarKeyCache = new Map<string, string>();
|
|
355
356
|
let avatarKeyCacheLoaded = false;
|
|
356
357
|
|
|
357
|
-
function normalizeAvatarTool(tool: string): string {
|
|
358
|
-
return AVATAR_BADGES[tool] ? tool :
|
|
359
|
-
}
|
|
358
|
+
function normalizeAvatarTool(tool: string): string {
|
|
359
|
+
return AVATAR_BADGES[tool] ? tool : PLAIN_AVATAR_TOOL;
|
|
360
|
+
}
|
|
360
361
|
|
|
361
362
|
function normalizeAvatarStatus(status: string): string {
|
|
362
363
|
return AVATAR_SOURCES[status] ? status : "idle";
|
|
363
364
|
}
|
|
364
365
|
|
|
365
|
-
function avatarCombinationPath(tool: string, status: string): string {
|
|
366
|
-
return resolvePath(AVATAR_COMBINATIONS_DIR, `avatar_${normalizeAvatarTool(tool)}_${normalizeAvatarStatus(status)}.png`);
|
|
367
|
-
}
|
|
366
|
+
function avatarCombinationPath(tool: string, status: string): string {
|
|
367
|
+
return resolvePath(AVATAR_COMBINATIONS_DIR, `avatar_${normalizeAvatarTool(tool)}_${normalizeAvatarStatus(status)}.png`);
|
|
368
|
+
}
|
|
368
369
|
|
|
369
370
|
function avatarCacheKey(
|
|
370
371
|
tool: string,
|
|
@@ -757,16 +758,19 @@ async function renderAvatar(
|
|
|
757
758
|
codexUsage: CodexUsageSummary | null = null,
|
|
758
759
|
cursorBatteryPercent: number | null = null,
|
|
759
760
|
): Promise<{ buffer: Buffer; contentType: string; filename: string }> {
|
|
760
|
-
const normalizedTool = normalizeAvatarTool(tool);
|
|
761
|
-
const normalizedStatus = normalizeAvatarStatus(status);
|
|
762
|
-
const composites: sharp.OverlayOptions[] = [];
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
const
|
|
766
|
-
const
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
761
|
+
const normalizedTool = normalizeAvatarTool(tool);
|
|
762
|
+
const normalizedStatus = normalizeAvatarStatus(status);
|
|
763
|
+
const composites: sharp.OverlayOptions[] = [];
|
|
764
|
+
const hasAgentBadge = normalizedTool !== PLAIN_AVATAR_TOOL;
|
|
765
|
+
|
|
766
|
+
const codexWeeklyUsage = normalizedTool === "codex" ? codexUsage?.weekly : null;
|
|
767
|
+
const useDynamicCodexAvatar = codexUsage && codexWeeklyUsage;
|
|
768
|
+
const useDynamicCursorAvatar = normalizedTool === "cursor" && cursorBatteryPercent !== null;
|
|
769
|
+
const basePath = useDynamicCodexAvatar || useDynamicCursorAvatar
|
|
770
|
+
? AVATAR_SOURCES[normalizedStatus]
|
|
771
|
+
: hasAgentBadge
|
|
772
|
+
? avatarCombinationPath(normalizedTool, normalizedStatus)
|
|
773
|
+
: AVATAR_SOURCES[normalizedStatus];
|
|
770
774
|
|
|
771
775
|
if (useDynamicCodexAvatar) {
|
|
772
776
|
composites.push(
|
|
@@ -793,16 +797,16 @@ async function renderAvatar(
|
|
|
793
797
|
.jpeg({ quality: 95, progressive: false })
|
|
794
798
|
.toBuffer();
|
|
795
799
|
|
|
796
|
-
return {
|
|
797
|
-
buffer: jpeg,
|
|
798
|
-
contentType: "image/jpeg",
|
|
799
|
-
filename: codexUsage?.weekly
|
|
800
|
-
? `avatar_${normalizedTool}_${normalizedStatus}_week_${codexUsage.weekly.remainingPercent}_5h_${codexUsage.fiveHour.remainingPercent}.jpg`
|
|
801
|
-
: cursorBatteryPercent !== null
|
|
802
|
-
? `avatar_${normalizedTool}_${normalizedStatus}_battery_${cursorBatteryPercent}.jpg`
|
|
803
|
-
: `avatar_${normalizedTool}_${normalizedStatus}.jpg`,
|
|
804
|
-
};
|
|
805
|
-
}
|
|
800
|
+
return {
|
|
801
|
+
buffer: jpeg,
|
|
802
|
+
contentType: "image/jpeg",
|
|
803
|
+
filename: normalizedTool === "codex" && codexUsage?.weekly
|
|
804
|
+
? `avatar_${normalizedTool}_${normalizedStatus}_week_${codexUsage.weekly.remainingPercent}_5h_${codexUsage.fiveHour.remainingPercent}.jpg`
|
|
805
|
+
: normalizedTool === "cursor" && cursorBatteryPercent !== null
|
|
806
|
+
? `avatar_${normalizedTool}_${normalizedStatus}_battery_${cursorBatteryPercent}.jpg`
|
|
807
|
+
: `avatar_${normalizedTool}_${normalizedStatus}.jpg`,
|
|
808
|
+
};
|
|
809
|
+
}
|
|
806
810
|
|
|
807
811
|
async function uploadImage(
|
|
808
812
|
token: string,
|