pi-better-openai 0.1.18 → 0.1.21
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 +90 -1
- package/index.ts +308 -1058
- package/package.json +9 -4
- package/src/codex-auth.ts +136 -0
- package/src/config.ts +297 -2
- package/src/fast-controller.ts +109 -0
- package/src/format.ts +81 -20
- package/src/image.ts +182 -200
- package/src/paths.ts +17 -0
- package/src/pet-footer-controller.ts +627 -0
- package/src/pets.ts +212 -63
- package/src/usage-controller.ts +299 -0
- package/src/usage.ts +99 -48
package/src/pets.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { constants, type Dirent } from "node:fs";
|
|
2
|
-
import { access, readdir, readFile,
|
|
2
|
+
import { access, lstat, readdir, readFile, realpath } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join, resolve, sep } from "node:path";
|
|
5
|
-
import type { ExtensionAPI, ExtensionContext } from "@
|
|
5
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import {
|
|
7
7
|
calculateImageRows,
|
|
8
8
|
getCapabilities,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
truncateToWidth,
|
|
13
13
|
type AutocompleteItem,
|
|
14
14
|
type ImageTheme,
|
|
15
|
-
} from "@
|
|
15
|
+
} from "@earendil-works/pi-tui";
|
|
16
16
|
import sharp from "sharp";
|
|
17
17
|
import { isRecord, type PetState } from "./config.ts";
|
|
18
18
|
|
|
@@ -24,6 +24,17 @@ const DEFAULT_CELL_HEIGHT = 208;
|
|
|
24
24
|
const DEFAULT_SPRITESHEET_PATH = "spritesheet.webp";
|
|
25
25
|
const EXPECTED_ATLAS_WIDTH = DEFAULT_CELL_WIDTH * PET_COLUMNS;
|
|
26
26
|
const EXPECTED_ATLAS_HEIGHT = DEFAULT_CELL_HEIGHT * PET_ROWS;
|
|
27
|
+
const PET_CATALOG_CACHE_TTL_MS = 1500;
|
|
28
|
+
const PET_CATALOG_READ_CONCURRENCY = 4;
|
|
29
|
+
|
|
30
|
+
type ListCodexPetsOptions = { refresh?: boolean };
|
|
31
|
+
|
|
32
|
+
type PetCatalogCacheEntry = {
|
|
33
|
+
expiresAt: number;
|
|
34
|
+
pets: CodexPetPackage[];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const petCatalogCache = new Map<string, PetCatalogCacheEntry>();
|
|
27
38
|
|
|
28
39
|
export const PET_ANIMATION_ROWS: Record<PetState, { row: number; durations: number[] }> = {
|
|
29
40
|
idle: { row: 0, durations: [280, 110, 110, 140, 140, 320] },
|
|
@@ -51,8 +62,8 @@ export type CodexPetPackage = {
|
|
|
51
62
|
export type PetFrame = {
|
|
52
63
|
data?: string;
|
|
53
64
|
rawRgbaData?: string;
|
|
65
|
+
kittyUpload?: string;
|
|
54
66
|
kittyImageId?: number;
|
|
55
|
-
kittyUploaded?: boolean;
|
|
56
67
|
mimeType: "image/png";
|
|
57
68
|
durationMs: number;
|
|
58
69
|
widthPx: number;
|
|
@@ -105,6 +116,10 @@ function sanitizePetAssetPathField(value: unknown): string | undefined {
|
|
|
105
116
|
return typeof value === "string" ? sanitizePetAssetPathText(value) : undefined;
|
|
106
117
|
}
|
|
107
118
|
|
|
119
|
+
function sanitizePetSlug(value: string): string {
|
|
120
|
+
return sanitizePetDisplayText(value) ?? "unnamed-pet";
|
|
121
|
+
}
|
|
122
|
+
|
|
108
123
|
export function codexHome(env = process.env, home = homedir()): string {
|
|
109
124
|
return env.CODEX_HOME?.trim() || join(home, ".codex");
|
|
110
125
|
}
|
|
@@ -132,11 +147,17 @@ function petInfoFromJson(
|
|
|
132
147
|
return { id, name, description, spritesheetPath };
|
|
133
148
|
}
|
|
134
149
|
|
|
150
|
+
function isPathInsideDirectory(parent: string, child: string): boolean {
|
|
151
|
+
const resolvedParent = resolve(parent);
|
|
152
|
+
const resolvedChild = resolve(child);
|
|
153
|
+
const prefix = resolvedParent.endsWith(sep) ? resolvedParent : `${resolvedParent}${sep}`;
|
|
154
|
+
return resolvedChild === resolvedParent || resolvedChild.startsWith(prefix);
|
|
155
|
+
}
|
|
156
|
+
|
|
135
157
|
function resolvePetAssetPath(petDir: string, path: string): string | undefined {
|
|
136
158
|
const resolvedPetDir = resolve(petDir);
|
|
137
159
|
const resolved = resolve(resolvedPetDir, path);
|
|
138
|
-
|
|
139
|
-
return resolved === resolvedPetDir || resolved.startsWith(prefix) ? resolved : undefined;
|
|
160
|
+
return isPathInsideDirectory(resolvedPetDir, resolved) ? resolved : undefined;
|
|
140
161
|
}
|
|
141
162
|
|
|
142
163
|
function formatSharpError(error: unknown): string {
|
|
@@ -156,13 +177,23 @@ async function validatePetSpritesheet(
|
|
|
156
177
|
|
|
157
178
|
let fileStat;
|
|
158
179
|
try {
|
|
159
|
-
fileStat = await
|
|
180
|
+
fileStat = await lstat(resolvedSpritesheetPath);
|
|
160
181
|
} catch {
|
|
161
182
|
return `missing ${spritesheetPath}`;
|
|
162
183
|
}
|
|
163
184
|
|
|
185
|
+
if (fileStat.isSymbolicLink()) return `${spritesheetPath} must not be a symlink`;
|
|
164
186
|
if (!fileStat.isFile()) return `${spritesheetPath} is not a file`;
|
|
165
187
|
|
|
188
|
+
const realPetDir = await realpath(petDir).catch(() => undefined);
|
|
189
|
+
const realSpritesheetPath = await realpath(resolvedSpritesheetPath).catch(() => undefined);
|
|
190
|
+
if (
|
|
191
|
+
!realPetDir ||
|
|
192
|
+
!realSpritesheetPath ||
|
|
193
|
+
!isPathInsideDirectory(realPetDir, realSpritesheetPath)
|
|
194
|
+
)
|
|
195
|
+
return `invalid spritesheetPath outside pet folder: ${spritesheetPath}`;
|
|
196
|
+
|
|
166
197
|
try {
|
|
167
198
|
await access(resolvedSpritesheetPath, constants.R_OK);
|
|
168
199
|
} catch {
|
|
@@ -181,47 +212,104 @@ async function validatePetSpritesheet(
|
|
|
181
212
|
return undefined;
|
|
182
213
|
}
|
|
183
214
|
|
|
184
|
-
|
|
215
|
+
function clearPetCatalogCache(home?: string): void {
|
|
216
|
+
if (home === undefined) {
|
|
217
|
+
petCatalogCache.clear();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
petCatalogCache.delete(resolve(home));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function readPetDirectoryEntries(home: string): Promise<{ dir: string; entries: Dirent[] }> {
|
|
185
224
|
const dir = codexPetsDir(home);
|
|
186
|
-
let entries: Dirent[];
|
|
187
225
|
try {
|
|
188
|
-
entries
|
|
226
|
+
return { dir, entries: await readdir(dir, { withFileTypes: true }) };
|
|
189
227
|
} catch {
|
|
190
|
-
return [];
|
|
228
|
+
return { dir, entries: [] };
|
|
191
229
|
}
|
|
230
|
+
}
|
|
192
231
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
slug
|
|
207
|
-
|
|
208
|
-
name,
|
|
209
|
-
description,
|
|
232
|
+
async function readCodexPetPackage(
|
|
233
|
+
petsDir: string,
|
|
234
|
+
entry: Dirent,
|
|
235
|
+
options: { validateSpritesheet: boolean },
|
|
236
|
+
): Promise<CodexPetPackage | undefined> {
|
|
237
|
+
if (!entry.isDirectory()) return undefined;
|
|
238
|
+
const petDir = join(petsDir, entry.name);
|
|
239
|
+
const slug = sanitizePetSlug(entry.name);
|
|
240
|
+
let parsed: unknown;
|
|
241
|
+
try {
|
|
242
|
+
parsed = JSON.parse(await readFile(join(petDir, "pet.json"), "utf8")) as unknown;
|
|
243
|
+
} catch (error) {
|
|
244
|
+
return {
|
|
245
|
+
slug,
|
|
246
|
+
name: sanitizePetDisplayText(entry.name) ?? "Unnamed pet",
|
|
210
247
|
dir: petDir,
|
|
211
|
-
spritesheetPath,
|
|
212
|
-
hasSpritesheet:
|
|
213
|
-
spritesheetIssue
|
|
214
|
-
}
|
|
248
|
+
spritesheetPath: DEFAULT_SPRITESHEET_PATH,
|
|
249
|
+
hasSpritesheet: false,
|
|
250
|
+
spritesheetIssue: `invalid pet.json: ${formatSharpError(error)}`,
|
|
251
|
+
};
|
|
215
252
|
}
|
|
216
|
-
|
|
253
|
+
const { id, name, description, spritesheetPath } = petInfoFromJson(parsed, entry.name);
|
|
254
|
+
const spritesheetIssue = options.validateSpritesheet
|
|
255
|
+
? await validatePetSpritesheet(petDir, spritesheetPath)
|
|
256
|
+
: undefined;
|
|
257
|
+
return {
|
|
258
|
+
slug,
|
|
259
|
+
id,
|
|
260
|
+
name,
|
|
261
|
+
description,
|
|
262
|
+
dir: petDir,
|
|
263
|
+
spritesheetPath,
|
|
264
|
+
hasSpritesheet: options.validateSpritesheet && spritesheetIssue === undefined,
|
|
265
|
+
spritesheetIssue,
|
|
266
|
+
};
|
|
217
267
|
}
|
|
218
268
|
|
|
219
|
-
function
|
|
220
|
-
|
|
269
|
+
async function readCodexPetPackages(
|
|
270
|
+
petsDir: string,
|
|
271
|
+
entries: Dirent[],
|
|
272
|
+
options: { validateSpritesheet: boolean },
|
|
273
|
+
): Promise<CodexPetPackage[]> {
|
|
274
|
+
const pets: Array<CodexPetPackage | undefined> = Array.from({ length: entries.length });
|
|
275
|
+
let nextIndex = 0;
|
|
276
|
+
const readNext = async (): Promise<void> => {
|
|
277
|
+
while (nextIndex < entries.length) {
|
|
278
|
+
const index = nextIndex++;
|
|
279
|
+
pets[index] = await readCodexPetPackage(petsDir, entries[index], options);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
await Promise.all(
|
|
283
|
+
Array.from({ length: Math.min(PET_CATALOG_READ_CONCURRENCY, entries.length) }, readNext),
|
|
284
|
+
);
|
|
285
|
+
return pets.filter((pet): pet is CodexPetPackage => pet !== undefined);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export async function listCodexPets(
|
|
289
|
+
home = codexHome(),
|
|
290
|
+
options: ListCodexPetsOptions = {},
|
|
291
|
+
): Promise<CodexPetPackage[]> {
|
|
292
|
+
const cacheKey = resolve(home);
|
|
293
|
+
const cached = petCatalogCache.get(cacheKey);
|
|
294
|
+
if (!options.refresh && cached && cached.expiresAt > Date.now()) return cached.pets;
|
|
295
|
+
|
|
296
|
+
const { dir, entries } = await readPetDirectoryEntries(home);
|
|
297
|
+
const pets = await readCodexPetPackages(dir, entries, { validateSpritesheet: true });
|
|
298
|
+
pets.sort((a, b) => a.name.localeCompare(b.name));
|
|
299
|
+
petCatalogCache.set(cacheKey, { expiresAt: Date.now() + PET_CATALOG_CACHE_TTL_MS, pets });
|
|
300
|
+
return pets;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function petLookupKey(value: string): string {
|
|
304
|
+
return value
|
|
305
|
+
.normalize("NFKC")
|
|
306
|
+
.toLowerCase()
|
|
307
|
+
.replace(/[^\p{L}\p{N}\p{M}]+/gu, "");
|
|
221
308
|
}
|
|
222
309
|
|
|
223
310
|
type PetLookupRequest = {
|
|
224
311
|
hasText: boolean;
|
|
312
|
+
exact?: string;
|
|
225
313
|
key?: string;
|
|
226
314
|
};
|
|
227
315
|
|
|
@@ -229,7 +317,11 @@ function petLookupRequest(value?: string): PetLookupRequest {
|
|
|
229
317
|
const requestedText = value?.trim();
|
|
230
318
|
if (!requestedText) return { hasText: false };
|
|
231
319
|
|
|
232
|
-
return {
|
|
320
|
+
return {
|
|
321
|
+
hasText: true,
|
|
322
|
+
exact: requestedText.normalize("NFKC").toLowerCase(),
|
|
323
|
+
key: petLookupKey(requestedText) || undefined,
|
|
324
|
+
};
|
|
233
325
|
}
|
|
234
326
|
|
|
235
327
|
function petMatchesLookup(pet: CodexPetPackage, requested: string): boolean {
|
|
@@ -240,11 +332,35 @@ function petMatchesLookup(pet: CodexPetPackage, requested: string): boolean {
|
|
|
240
332
|
);
|
|
241
333
|
}
|
|
242
334
|
|
|
335
|
+
function exactPetMatches(
|
|
336
|
+
pets: CodexPetPackage[],
|
|
337
|
+
requested: string,
|
|
338
|
+
): CodexPetPackage[] | undefined {
|
|
339
|
+
const fields: Array<(pet: CodexPetPackage) => string | undefined> = [
|
|
340
|
+
(pet) => pet.slug,
|
|
341
|
+
(pet) => pet.id,
|
|
342
|
+
(pet) => pet.name,
|
|
343
|
+
];
|
|
344
|
+
for (const field of fields) {
|
|
345
|
+
const matches = pets.filter((pet) => field(pet)?.normalize("NFKC").toLowerCase() === requested);
|
|
346
|
+
if (matches.length > 0) return matches;
|
|
347
|
+
}
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function uniquePetMatch(pets: CodexPetPackage[]): CodexPetPackage | undefined {
|
|
352
|
+
return pets.length === 1 ? pets[0] : undefined;
|
|
353
|
+
}
|
|
354
|
+
|
|
243
355
|
export function findCodexPet(pets: CodexPetPackage[], value?: string): CodexPetPackage | undefined {
|
|
244
356
|
const request = petLookupRequest(value);
|
|
245
357
|
const { key } = request;
|
|
246
358
|
if (!key) return undefined;
|
|
247
|
-
|
|
359
|
+
if (request.exact) {
|
|
360
|
+
const exactMatches = exactPetMatches(pets, request.exact);
|
|
361
|
+
if (exactMatches) return uniquePetMatch(exactMatches);
|
|
362
|
+
}
|
|
363
|
+
return uniquePetMatch(pets.filter((pet) => petMatchesLookup(pet, key)));
|
|
248
364
|
}
|
|
249
365
|
|
|
250
366
|
export function findReadyCodexPet(
|
|
@@ -256,7 +372,14 @@ export function findReadyCodexPet(
|
|
|
256
372
|
|
|
257
373
|
const { key } = request;
|
|
258
374
|
if (!key) return undefined;
|
|
259
|
-
|
|
375
|
+
if (request.exact) {
|
|
376
|
+
const exactMatches = exactPetMatches(pets, request.exact);
|
|
377
|
+
if (exactMatches) {
|
|
378
|
+
const exact = uniquePetMatch(exactMatches);
|
|
379
|
+
return exact?.hasSpritesheet ? exact : undefined;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return uniquePetMatch(pets.filter((pet) => pet.hasSpritesheet && petMatchesLookup(pet, key)));
|
|
260
383
|
}
|
|
261
384
|
|
|
262
385
|
function selectPet(pets: CodexPetPackage[], slug?: string): CodexPetPackage | undefined {
|
|
@@ -356,13 +479,33 @@ function kittyImageBaseForPet(slug: string): number {
|
|
|
356
479
|
return 0x50000000 + hash * 100;
|
|
357
480
|
}
|
|
358
481
|
|
|
482
|
+
async function findCodexPetDirect(
|
|
483
|
+
slug: string,
|
|
484
|
+
home: string,
|
|
485
|
+
): Promise<CodexPetPackage | undefined> {
|
|
486
|
+
const request = petLookupRequest(slug);
|
|
487
|
+
if (!request.key) return undefined;
|
|
488
|
+
const { dir, entries } = await readPetDirectoryEntries(home);
|
|
489
|
+
const candidates = await readCodexPetPackages(dir, entries, { validateSpritesheet: false });
|
|
490
|
+
const candidate = findCodexPet(candidates, slug);
|
|
491
|
+
if (!candidate || candidate.spritesheetIssue) return candidate;
|
|
492
|
+
const spritesheetIssue = await validatePetSpritesheet(candidate.dir, candidate.spritesheetPath);
|
|
493
|
+
return {
|
|
494
|
+
...candidate,
|
|
495
|
+
hasSpritesheet: spritesheetIssue === undefined,
|
|
496
|
+
spritesheetIssue,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
359
500
|
export async function loadCodexPet(
|
|
360
501
|
slug?: string,
|
|
361
502
|
home = codexHome(),
|
|
362
503
|
options: { sizeCells?: number } = {},
|
|
363
504
|
): Promise<LoadedCodexPet | undefined> {
|
|
364
|
-
const pet =
|
|
365
|
-
|
|
505
|
+
const pet = slug
|
|
506
|
+
? await findCodexPetDirect(slug, home)
|
|
507
|
+
: selectPet(await listCodexPets(home), slug);
|
|
508
|
+
if (!pet?.hasSpritesheet) return undefined;
|
|
366
509
|
|
|
367
510
|
const spritesheetPath = resolvePetAssetPath(pet.dir, pet.spritesheetPath);
|
|
368
511
|
if (!spritesheetPath)
|
|
@@ -422,12 +565,20 @@ export async function loadCodexPet(
|
|
|
422
565
|
kernel: sharp.kernel.nearest,
|
|
423
566
|
});
|
|
424
567
|
}
|
|
568
|
+
const kittyImageId = useKitty ? kittyImageBase + kittyFrameOffset++ : undefined;
|
|
425
569
|
const encoded = useKitty
|
|
426
|
-
? {
|
|
427
|
-
|
|
570
|
+
? {
|
|
571
|
+
kittyUpload: encodeKittyRawRgbaData(
|
|
572
|
+
(await frame.ensureAlpha().raw().toBuffer()).toString("base64"),
|
|
573
|
+
outputWidthPx,
|
|
574
|
+
outputHeightPx,
|
|
575
|
+
kittyImageId!,
|
|
576
|
+
),
|
|
577
|
+
}
|
|
578
|
+
: { data: (await frame.png().toBuffer()).toString("base64") };
|
|
428
579
|
states[state].push({
|
|
429
580
|
...encoded,
|
|
430
|
-
kittyImageId
|
|
581
|
+
kittyImageId,
|
|
431
582
|
mimeType: "image/png",
|
|
432
583
|
durationMs,
|
|
433
584
|
widthPx: outputWidthPx,
|
|
@@ -484,12 +635,6 @@ function forEachCodexPetFrame(
|
|
|
484
635
|
}
|
|
485
636
|
}
|
|
486
637
|
|
|
487
|
-
function markCodexPetKittyFramesUnuploaded(pet?: LoadedCodexPet): void {
|
|
488
|
-
forEachCodexPetFrame(pet, (frame) => {
|
|
489
|
-
frame.kittyUploaded = false;
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
|
|
493
638
|
function codexPetKittyImageIds(pet?: LoadedCodexPet): number[] {
|
|
494
639
|
const imageIds = new Set<number>();
|
|
495
640
|
forEachCodexPetFrame(pet, (frame) => {
|
|
@@ -510,18 +655,15 @@ function placeKittyImage(imageId: number, columns: number, rows: number): string
|
|
|
510
655
|
return `\x1b_Ga=p,i=${imageId},p=1,c=${columns},r=${rows},q=2,C=1\x1b\\`;
|
|
511
656
|
}
|
|
512
657
|
|
|
513
|
-
function
|
|
514
|
-
|
|
658
|
+
function encodeKittyRawRgbaData(
|
|
659
|
+
rawRgbaData: string,
|
|
660
|
+
widthPx: number,
|
|
661
|
+
heightPx: number,
|
|
662
|
+
imageId: number,
|
|
663
|
+
): string {
|
|
515
664
|
if (!rawRgbaData) return "";
|
|
516
665
|
const chunkSize = 4096;
|
|
517
|
-
const params = [
|
|
518
|
-
"a=t",
|
|
519
|
-
"f=32",
|
|
520
|
-
`s=${frame.widthPx}`,
|
|
521
|
-
`v=${frame.heightPx}`,
|
|
522
|
-
`i=${imageId}`,
|
|
523
|
-
"q=2",
|
|
524
|
-
].join(",");
|
|
666
|
+
const params = ["a=t", "f=32", `s=${widthPx}`, `v=${heightPx}`, `i=${imageId}`, "q=2"].join(",");
|
|
525
667
|
if (rawRgbaData.length <= chunkSize) {
|
|
526
668
|
return `\x1b_G${params};${rawRgbaData}\x1b\\`;
|
|
527
669
|
}
|
|
@@ -537,6 +679,15 @@ function encodeKittyRawRgba(frame: PetFrame, imageId: number): string {
|
|
|
537
679
|
return chunks.join("");
|
|
538
680
|
}
|
|
539
681
|
|
|
682
|
+
function encodeKittyRawRgba(frame: PetFrame, imageId: number): string {
|
|
683
|
+
return (
|
|
684
|
+
frame.kittyUpload ??
|
|
685
|
+
(frame.rawRgbaData
|
|
686
|
+
? encodeKittyRawRgbaData(frame.rawRgbaData, frame.widthPx, frame.heightPx, imageId)
|
|
687
|
+
: "")
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
|
|
540
691
|
export class CodexPetKittyManager {
|
|
541
692
|
private previousFrameImageId: number | undefined;
|
|
542
693
|
private readonly pendingCleanupImageIds = new Set<number>();
|
|
@@ -550,7 +701,6 @@ export class CodexPetKittyManager {
|
|
|
550
701
|
invalidate(pet?: LoadedCodexPet): void {
|
|
551
702
|
this.queueCleanup(pet);
|
|
552
703
|
this.previousFrameImageId = undefined;
|
|
553
|
-
markCodexPetKittyFramesUnuploaded(pet);
|
|
554
704
|
}
|
|
555
705
|
|
|
556
706
|
resetForResize(pet?: LoadedCodexPet): void {
|
|
@@ -592,7 +742,6 @@ export class CodexPetKittyManager {
|
|
|
592
742
|
// occasionally invisible pet. The TUI diff still avoids writing this payload
|
|
593
743
|
// again when the rendered line is unchanged.
|
|
594
744
|
const upload = encodeKittyRawRgba(frame, frameImageId);
|
|
595
|
-
frame.kittyUploaded = true;
|
|
596
745
|
this.previousFrameImageId = frameImageId;
|
|
597
746
|
// Recent pi-tui versions automatically free Kitty image IDs they own on changed lines.
|
|
598
747
|
// Pet frames are managed here and intentionally reused across animation loops, so keep
|
|
@@ -625,7 +774,6 @@ function defaultKittyManager(placementImageId: number): CodexPetKittyManager {
|
|
|
625
774
|
export function resetCodexPetKittyCache(pet?: LoadedCodexPet, placementImageId?: number): void {
|
|
626
775
|
if (placementImageId !== undefined) defaultKittyManagers.get(placementImageId)?.invalidate(pet);
|
|
627
776
|
else for (const manager of defaultKittyManagers.values()) manager.invalidate(pet);
|
|
628
|
-
markCodexPetKittyFramesUnuploaded(pet);
|
|
629
777
|
}
|
|
630
778
|
|
|
631
779
|
export function renderCodexPetFrame(
|
|
@@ -773,7 +921,7 @@ export function formatCodexPetsListMessage(pets: CodexPetPackage[], home = codex
|
|
|
773
921
|
|
|
774
922
|
async function notifyPetsList(ctx: ExtensionContext): Promise<void> {
|
|
775
923
|
const home = codexHome();
|
|
776
|
-
const pets = await listCodexPets(home);
|
|
924
|
+
const pets = await listCodexPets(home, { refresh: true });
|
|
777
925
|
ctx.ui.notify(
|
|
778
926
|
formatCodexPetsListMessage(pets, home),
|
|
779
927
|
pets.some((pet) => pet.hasSpritesheet) ? "info" : "warning",
|
|
@@ -827,4 +975,5 @@ export const _petsTest = {
|
|
|
827
975
|
findCodexPet,
|
|
828
976
|
findReadyCodexPet,
|
|
829
977
|
selectPet,
|
|
978
|
+
clearPetCatalogCache,
|
|
830
979
|
};
|