@harness-mix/cli 0.1.9 → 0.1.11

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +25 -30
  3. package/output/native-build/desktop-controller.mjs +1 -1
  4. package/output/native-build/renderer-extension.js +703 -41
  5. package/package.json +9 -8
  6. package/scripts/native-config-test.cjs +32 -2
  7. package/scripts/native-ui-smoke.cjs +39 -4
  8. package/scripts/pets-test.cjs +146 -0
  9. package/src/main/native/launcher.js +49 -2
  10. package/src/main/native/pets.js +176 -15
  11. package/src/main/native/protocol.js +9 -8
  12. package/src/native-ui/desktop-control/dist/renderer-draft-prewarm-runtime.js +4 -4
  13. package/src/native-ui/desktop-control/dist/renderer-draft-prewarm-runtime.js.map +1 -1
  14. package/src/native-ui/desktop-control/dist/tsconfig.tsbuildinfo +1 -1
  15. package/src/native-ui/desktop-control/src/renderer-draft-prewarm-runtime.ts +4 -4
  16. package/src/native-ui/desktop-control/test/renderer-draft-prewarm-policy.test.ts +7 -0
  17. package/src/native-ui/renderer-extension/dist/types/agent-selection-state.d.ts +1 -0
  18. package/src/native-ui/renderer-extension/dist/types/agent-selection-state.d.ts.map +1 -1
  19. package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts +1 -0
  20. package/src/native-ui/renderer-extension/dist/types/renderer-binding-probe.d.ts.map +1 -1
  21. package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts +2 -0
  22. package/src/native-ui/renderer-extension/dist/types/renderer-model-client.d.ts.map +1 -1
  23. package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts +2 -1
  24. package/src/native-ui/renderer-extension/dist/types/renderer-settings-lifecycle.d.ts.map +1 -1
  25. package/src/native-ui/renderer-extension/dist/types/settings/icons.d.ts +1 -1
  26. package/src/native-ui/renderer-extension/dist/types/settings/icons.d.ts.map +1 -1
  27. package/src/native-ui/renderer-extension/dist/types/settings/localization.d.ts.map +1 -1
  28. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts +5 -3
  29. package/src/native-ui/renderer-extension/dist/types/settings/pages.d.ts.map +1 -1
  30. package/src/native-ui/renderer-extension/dist/types/settings/pet-market.d.ts +5 -0
  31. package/src/native-ui/renderer-extension/dist/types/settings/pet-market.d.ts.map +1 -0
  32. package/src/native-ui/renderer-extension/dist/types/settings/pets-client.d.ts +57 -0
  33. package/src/native-ui/renderer-extension/dist/types/settings/pets-client.d.ts.map +1 -0
  34. package/src/native-ui/renderer-extension/dist/types/settings/shell.d.ts.map +1 -1
  35. package/src/native-ui/renderer-extension/dist/types/settings/skin-runtime.d.ts.map +1 -1
  36. package/src/native-ui/renderer-extension/dist/types/tsconfig.tsbuildinfo +1 -1
  37. package/src/native-ui/renderer-extension/src/agent-selection-state.ts +45 -14
  38. package/src/native-ui/renderer-extension/src/renderer-binding-probe.ts +69 -27
  39. package/src/native-ui/renderer-extension/src/renderer-model-client.ts +6 -0
  40. package/src/native-ui/renderer-extension/src/renderer-settings-lifecycle.ts +3 -0
  41. package/src/native-ui/renderer-extension/src/settings/icons.ts +15 -12
  42. package/src/native-ui/renderer-extension/src/settings/localization.ts +2 -0
  43. package/src/native-ui/renderer-extension/src/settings/pages.ts +74 -66
  44. package/src/native-ui/renderer-extension/src/settings/pet-market.css +310 -0
  45. package/src/native-ui/renderer-extension/src/settings/pet-market.ts +447 -0
  46. package/src/native-ui/renderer-extension/src/settings/pets-client.ts +74 -0
  47. package/src/native-ui/renderer-extension/src/settings/shell.css +27 -8
  48. package/src/native-ui/renderer-extension/src/settings/shell.ts +4 -3
  49. package/src/native-ui/renderer-extension/src/settings/skin-runtime.ts +234 -3
  50. package/src/native-ui/renderer-extension/test/agent-selection-state.test.ts +58 -0
  51. package/src/native-ui/renderer-extension/test/renderer-binding-probe.test.ts +12 -0
  52. package/src/native-ui/renderer-extension/test/renderer-model-client.test.ts +1 -0
  53. package/src/native-ui/renderer-extension/test/settings/localization.test.ts +1 -0
  54. package/src/native-ui/renderer-extension/test/settings/shell.test.ts +3 -0
  55. package/src/native-ui/renderer-extension/test/settings/skin-runtime.test.ts +28 -2
@@ -0,0 +1,447 @@
1
+ import type { RendererSettingsMessages } from "./localization.js";
2
+ import type {
3
+ RendererSettingsPageDefinition,
4
+ RendererSettingsPageMountContext,
5
+ } from "./core.js";
6
+ import { createRendererSettingsIcon } from "./icons.js";
7
+ import type { RendererPetsClient, RendererPetItem } from "./pets-client.js";
8
+
9
+ const COPY = {
10
+ en: {
11
+ title: "Pets",
12
+ intro:
13
+ "Add companion pets to Codex Desktop. Installed companions sync directly with Codex native pets directory and respond to agent state.",
14
+ safe: "Native local integration",
15
+ safeDetail:
16
+ "Pets install into ~/.codex/pets/, containing only spritesheets and metadata without running external scripts. Instantly selectable in Codex settings.",
17
+ all: "All",
18
+ official: "Official Preload",
19
+ community: "Community",
20
+ installed: "Installed",
21
+ searchPlaceholder: "Search pets by name or description...",
22
+ install: "Install Pet",
23
+ installing: "Installing...",
24
+ installedBadge: "Installed",
25
+ uninstall: "Uninstall",
26
+ uninstalling: "Removing...",
27
+ tip: "Select in Appearance > Pets or type /pet to wake",
28
+ empty: "No pets match the current filter.",
29
+ officialBadge: "Official",
30
+ communityBadge: "Community",
31
+ },
32
+ "zh-CN": {
33
+ title: "桌宠市场",
34
+ intro:
35
+ "为 Codex 添加桌面萌宠伴侣。安装后自动同步至 Codex Desktop 原生桌宠目录,随 AI 状态实时交互动画。",
36
+ safe: "本地原生集成",
37
+ safeDetail:
38
+ "桌宠安装于 ~/.codex/pets/,仅包含动画精灵图与元数据,不执行外部代码。在 Codex 官方外观设置中立即可选。",
39
+ all: "全部",
40
+ official: "官方预载",
41
+ community: "社区精选",
42
+ installed: "已安装",
43
+ searchPlaceholder: "搜索桌宠名称或描述...",
44
+ install: "安装桌宠",
45
+ installing: "安装中...",
46
+ installedBadge: "已安装",
47
+ uninstall: "卸载",
48
+ uninstalling: "卸载中...",
49
+ tip: "安装后在官方设置「外观 > 桌宠」或输入 /pet 指令唤醒伴侣",
50
+ empty: "没有找到符合条件的桌宠。",
51
+ officialBadge: "官方预载",
52
+ communityBadge: "社区精选",
53
+ },
54
+ } as const;
55
+
56
+ type PetTab = "all" | "official" | "community" | "installed";
57
+
58
+ const SPRITE_COLUMNS = 8;
59
+ const SPRITE_FRAMES = 6;
60
+ const FRAME_DURATION_MS = 220;
61
+
62
+ function setupSpriteAnimation(
63
+ element: HTMLElement,
64
+ imageUrl: string,
65
+ spriteVersion = 2,
66
+ ): () => void {
67
+ const rows = spriteVersion === 1 ? 9 : 11;
68
+ element.style.backgroundImage = `url("${imageUrl}")`;
69
+ element.style.backgroundSize = `${SPRITE_COLUMNS * 100}% ${rows * 100}%`;
70
+ element.style.backgroundPosition = "0% 0%";
71
+
72
+ let currentFrame = 0;
73
+ let timer: number | null = null;
74
+ let isHovered = false;
75
+
76
+ const tick = () => {
77
+ currentFrame = (currentFrame + 1) % SPRITE_FRAMES;
78
+ const posX = (currentFrame / (SPRITE_COLUMNS - 1)) * 100;
79
+ element.style.backgroundPosition = `${posX}% 0%`;
80
+ };
81
+
82
+ const startLoop = () => {
83
+ if (timer !== null) return;
84
+ timer = window.setInterval(tick, FRAME_DURATION_MS);
85
+ };
86
+
87
+ const stopLoop = () => {
88
+ if (timer !== null) {
89
+ clearInterval(timer);
90
+ timer = null;
91
+ }
92
+ if (!isHovered) {
93
+ currentFrame = 0;
94
+ element.style.backgroundPosition = "0% 0%";
95
+ }
96
+ };
97
+
98
+ const onMouseEnter = () => {
99
+ isHovered = true;
100
+ startLoop();
101
+ };
102
+
103
+ const onMouseLeave = () => {
104
+ isHovered = false;
105
+ stopLoop();
106
+ };
107
+
108
+ const parentCard = element.closest(".pet-card");
109
+ if (parentCard) {
110
+ parentCard.addEventListener("mouseenter", onMouseEnter);
111
+ parentCard.addEventListener("mouseleave", onMouseLeave);
112
+ }
113
+
114
+ // Periodic idle blink/movement every few seconds
115
+ const idleInterval = window.setInterval(() => {
116
+ if (!isHovered && timer === null) {
117
+ startLoop();
118
+ setTimeout(stopLoop, FRAME_DURATION_MS * SPRITE_FRAMES);
119
+ }
120
+ }, 4000 + Math.random() * 3000);
121
+
122
+ return () => {
123
+ stopLoop();
124
+ clearInterval(idleInterval);
125
+ if (parentCard) {
126
+ parentCard.removeEventListener("mouseenter", onMouseEnter);
127
+ parentCard.removeEventListener("mouseleave", onMouseLeave);
128
+ }
129
+ };
130
+ }
131
+
132
+ export function createPetSettingsPage(
133
+ messages: RendererSettingsMessages,
134
+ getClient: () => RendererPetsClient | null,
135
+ ): RendererSettingsPageDefinition {
136
+ const copy = COPY[messages.locale];
137
+
138
+ return Object.freeze({
139
+ id: "pets",
140
+ label: messages.pageLabels.pets,
141
+ icon: "pets",
142
+ mount(context: RendererSettingsPageMountContext) {
143
+ const document = context.content.ownerDocument;
144
+ const disposers: Array<() => void> = [];
145
+
146
+ const heading = document.createElement("div");
147
+ heading.className = "settings-section-label";
148
+ heading.textContent = copy.title;
149
+
150
+ const intro = document.createElement("p");
151
+ intro.className = "pet-market__intro";
152
+ intro.textContent = copy.intro;
153
+
154
+ const safety = document.createElement("aside");
155
+ safety.className = "pet-safety-note";
156
+ safety.append(createRendererSettingsIcon("shield", 18));
157
+ const safetyBody = document.createElement("span");
158
+ const safetyTitle = document.createElement("strong");
159
+ safetyTitle.textContent = copy.safe;
160
+ const safetyDetail = document.createElement("span");
161
+ safetyDetail.textContent = copy.safeDetail;
162
+ safetyBody.append(safetyTitle, safetyDetail);
163
+ safety.append(safetyBody);
164
+
165
+ // Toolbar: tabs + search
166
+ const toolbar = document.createElement("div");
167
+ toolbar.className = "pet-market__toolbar";
168
+
169
+ const tabsWrap = document.createElement("div");
170
+ tabsWrap.className = "pet-market__tabs";
171
+
172
+ let activeTab: PetTab = "all";
173
+ let searchQuery = "";
174
+ const tabButtons = new Map<PetTab, HTMLButtonElement>();
175
+
176
+ const tabs: Array<{ id: PetTab; label: string }> = [
177
+ { id: "all", label: copy.all },
178
+ { id: "official", label: copy.official },
179
+ { id: "community", label: copy.community },
180
+ { id: "installed", label: copy.installed },
181
+ ];
182
+
183
+ const searchWrap = document.createElement("div");
184
+ searchWrap.className = "pet-market__search-wrap";
185
+ searchWrap.append(createRendererSettingsIcon("search", 15));
186
+
187
+ const searchInput = document.createElement("input");
188
+ searchInput.type = "search";
189
+ searchInput.className = "pet-market__search";
190
+ searchInput.placeholder = copy.searchPlaceholder;
191
+ searchWrap.append(searchInput);
192
+
193
+ toolbar.append(tabsWrap, searchWrap);
194
+
195
+ const grid = document.createElement("div");
196
+ grid.className = "pet-market-grid";
197
+
198
+ context.content.append(heading, intro, safety, toolbar, grid);
199
+
200
+ let allPets: RendererPetItem[] = [];
201
+ const imageCache = new Map<string, string>(); // id -> dataUrl / url
202
+
203
+ const renderGrid = () => {
204
+ // Clear previous animations
205
+ for (const d of disposers) d();
206
+ disposers.length = 0;
207
+ grid.replaceChildren();
208
+
209
+ const query = searchQuery.trim().toLowerCase();
210
+ const filtered = allPets.filter((pet) => {
211
+ if (activeTab === "official" && pet.source !== "official") return false;
212
+ if (activeTab === "community" && pet.source !== "community") return false;
213
+ if (activeTab === "installed" && !pet.installed) return false;
214
+ if (query) {
215
+ const matchesName = pet.displayName.toLowerCase().includes(query);
216
+ const matchesDesc = (pet.description || "").toLowerCase().includes(query);
217
+ const matchesId = pet.id.toLowerCase().includes(query);
218
+ if (!matchesName && !matchesDesc && !matchesId) return false;
219
+ }
220
+ return true;
221
+ });
222
+
223
+ if (filtered.length === 0) {
224
+ const empty = document.createElement("div");
225
+ empty.className = "pet-market__empty";
226
+ empty.textContent = copy.empty;
227
+ grid.append(empty);
228
+ return;
229
+ }
230
+
231
+ for (const pet of filtered) {
232
+ const card = document.createElement("article");
233
+ card.className = "pet-card";
234
+ card.dataset.petId = pet.id;
235
+ card.dataset.installed = String(pet.installed);
236
+
237
+ // Stage
238
+ const stage = document.createElement("div");
239
+ stage.className = "pet-card__stage";
240
+
241
+ const sprite = document.createElement("div");
242
+ sprite.className = "pet-card__sprite";
243
+ stage.append(sprite);
244
+
245
+ // Load visual asset
246
+ const client = getClient();
247
+ const cachedImg = imageCache.get(pet.id);
248
+ if (cachedImg) {
249
+ const disposeAnim = setupSpriteAnimation(sprite, cachedImg, pet.spriteVersionNumber);
250
+ disposers.push(disposeAnim);
251
+ } else if (pet.spritesheetUrl) {
252
+ imageCache.set(pet.id, pet.spritesheetUrl);
253
+ const disposeAnim = setupSpriteAnimation(
254
+ sprite,
255
+ pet.spritesheetUrl,
256
+ pet.spriteVersionNumber,
257
+ );
258
+ disposers.push(disposeAnim);
259
+ } else if (client) {
260
+ // Load base64 preview on demand
261
+ void client.preview(pet.id).then(
262
+ (res) => {
263
+ if (res && res.dataBase64) {
264
+ const dataUrl = `data:${res.mime};base64,${res.dataBase64}`;
265
+ imageCache.set(pet.id, dataUrl);
266
+ const disposeAnim = setupSpriteAnimation(
267
+ sprite,
268
+ dataUrl,
269
+ pet.spriteVersionNumber,
270
+ );
271
+ disposers.push(disposeAnim);
272
+ }
273
+ },
274
+ () => {
275
+ if (pet.previewUrl) {
276
+ sprite.style.backgroundImage = `url("${pet.previewUrl}")`;
277
+ sprite.style.backgroundSize = "contain";
278
+ sprite.style.backgroundPosition = "center";
279
+ }
280
+ },
281
+ );
282
+ } else if (pet.previewUrl) {
283
+ sprite.style.backgroundImage = `url("${pet.previewUrl}")`;
284
+ sprite.style.backgroundSize = "contain";
285
+ sprite.style.backgroundPosition = "center";
286
+ }
287
+
288
+ // Body
289
+ const body = document.createElement("div");
290
+ body.className = "pet-card__body";
291
+
292
+ const cardHeader = document.createElement("div");
293
+ cardHeader.className = "pet-card__header";
294
+
295
+ const title = document.createElement("strong");
296
+ title.className = "pet-card__title";
297
+ title.textContent = pet.displayName;
298
+
299
+ const badges = document.createElement("div");
300
+ badges.className = "pet-card__badges";
301
+
302
+ if (pet.installed) {
303
+ const b = document.createElement("span");
304
+ b.className = "pet-card__badge pet-card__badge--installed";
305
+ b.append(createRendererSettingsIcon("check", 12), copy.installedBadge);
306
+ badges.append(b);
307
+ } else if (pet.source === "official") {
308
+ const b = document.createElement("span");
309
+ b.className = "pet-card__badge pet-card__badge--official";
310
+ b.textContent = copy.officialBadge;
311
+ badges.append(b);
312
+ } else if (pet.source === "community") {
313
+ const b = document.createElement("span");
314
+ b.className = "pet-card__badge pet-card__badge--community";
315
+ b.textContent = copy.communityBadge;
316
+ badges.append(b);
317
+ }
318
+
319
+ cardHeader.append(title, badges);
320
+
321
+ const desc = document.createElement("p");
322
+ desc.className = "pet-card__description";
323
+ desc.textContent = pet.description || "";
324
+
325
+ // Footer & Actions
326
+ const footer = document.createElement("div");
327
+ footer.className = "pet-card__footer";
328
+
329
+ const hint = document.createElement("span");
330
+ hint.className = "pet-card__dir-hint";
331
+ hint.textContent = copy.tip;
332
+
333
+ const actions = document.createElement("div");
334
+ actions.className = "pet-card__actions";
335
+
336
+ if (pet.installed) {
337
+ const uninstallBtn = document.createElement("button");
338
+ uninstallBtn.type = "button";
339
+ uninstallBtn.className = "pet-btn pet-btn--danger";
340
+ uninstallBtn.textContent = copy.uninstall;
341
+ uninstallBtn.addEventListener("click", () => {
342
+ if (!client) return;
343
+ uninstallBtn.disabled = true;
344
+ uninstallBtn.textContent = copy.uninstalling;
345
+ void client.uninstall(pet.id).then(
346
+ () => {
347
+ allPets = allPets.map((p) =>
348
+ p.id === pet.id ? { ...p, installed: false } : p,
349
+ );
350
+ renderGrid();
351
+ },
352
+ (err) => {
353
+ uninstallBtn.disabled = false;
354
+ uninstallBtn.textContent = copy.uninstall;
355
+ alert(err instanceof Error ? err.message : String(err));
356
+ },
357
+ );
358
+ });
359
+ actions.append(uninstallBtn);
360
+ } else {
361
+ const installBtn = document.createElement("button");
362
+ installBtn.type = "button";
363
+ installBtn.className = "pet-btn pet-btn--primary";
364
+ installBtn.textContent = copy.install;
365
+ installBtn.addEventListener("click", () => {
366
+ if (!client) return;
367
+ installBtn.disabled = true;
368
+ installBtn.textContent = copy.installing;
369
+ void client
370
+ .install({
371
+ id: pet.id,
372
+ displayName: pet.displayName,
373
+ description: pet.description,
374
+ spritesheetUrl: pet.spritesheetUrl,
375
+ spriteVersionNumber: pet.spriteVersionNumber,
376
+ })
377
+ .then(
378
+ () => {
379
+ allPets = allPets.map((p) =>
380
+ p.id === pet.id ? { ...p, installed: true } : p,
381
+ );
382
+ renderGrid();
383
+ },
384
+ (err) => {
385
+ installBtn.disabled = false;
386
+ installBtn.textContent = copy.install;
387
+ alert(err instanceof Error ? err.message : String(err));
388
+ },
389
+ );
390
+ });
391
+ actions.append(installBtn);
392
+ }
393
+
394
+ footer.append(hint, actions);
395
+ body.append(cardHeader, desc, footer);
396
+ card.append(stage, body);
397
+ grid.append(card);
398
+ }
399
+ };
400
+
401
+ for (const t of tabs) {
402
+ const btn = document.createElement("button");
403
+ btn.type = "button";
404
+ btn.className = "pet-market__tab";
405
+ btn.textContent = t.label;
406
+ btn.dataset.active = String(t.id === activeTab);
407
+ btn.addEventListener("click", () => {
408
+ activeTab = t.id;
409
+ for (const [tabId, b] of tabButtons) {
410
+ b.dataset.active = String(tabId === activeTab);
411
+ }
412
+ renderGrid();
413
+ });
414
+ tabButtons.set(t.id, btn);
415
+ tabsWrap.append(btn);
416
+ }
417
+
418
+ searchInput.addEventListener("input", () => {
419
+ searchQuery = searchInput.value;
420
+ renderGrid();
421
+ });
422
+
423
+ // Fetch catalog
424
+ const client = getClient();
425
+ if (client) {
426
+ void context.runLatest(
427
+ () => client.catalog(),
428
+ {
429
+ success(result) {
430
+ allPets = [...result.data];
431
+ renderGrid();
432
+ },
433
+ failure(error) {
434
+ grid.textContent =
435
+ error instanceof Error ? error.message : String(error);
436
+ },
437
+ },
438
+ );
439
+ }
440
+
441
+ return () => {
442
+ for (const d of disposers) d();
443
+ disposers.length = 0;
444
+ };
445
+ },
446
+ });
447
+ }
@@ -0,0 +1,74 @@
1
+ export interface RendererPetItem {
2
+ readonly id: string;
3
+ readonly displayName: string;
4
+ readonly description?: string | undefined;
5
+ readonly source: "official" | "community" | "installed";
6
+ readonly installed: boolean;
7
+ readonly spriteVersionNumber?: number | undefined;
8
+ readonly spritesheetUrl?: string | undefined;
9
+ readonly previewUrl?: string | undefined;
10
+ readonly posterUrl?: string | undefined;
11
+ readonly downloadUrl?: string | undefined;
12
+ }
13
+
14
+ export interface RendererPetCatalogResult {
15
+ readonly data: readonly RendererPetItem[];
16
+ readonly officialAvailable: boolean;
17
+ readonly petsDir: string;
18
+ }
19
+
20
+ export interface RendererPetCommunityResult {
21
+ readonly data: readonly RendererPetItem[];
22
+ readonly total: number;
23
+ readonly source: string;
24
+ }
25
+
26
+ export interface RendererPetPreviewResult {
27
+ readonly id: string;
28
+ readonly mime: string;
29
+ readonly dataBase64: string;
30
+ }
31
+
32
+ export interface RendererPetInstallParams {
33
+ readonly id: string;
34
+ readonly displayName?: string | undefined;
35
+ readonly description?: string | undefined;
36
+ readonly spritesheetUrl?: string | undefined;
37
+ readonly spriteVersionNumber?: number | undefined;
38
+ }
39
+
40
+ export interface RendererPetInstallResult {
41
+ readonly id: string;
42
+ readonly path: string;
43
+ readonly installed: boolean;
44
+ readonly alreadyInstalled?: boolean | undefined;
45
+ }
46
+
47
+ export interface RendererPetsClient {
48
+ catalog(): Promise<RendererPetCatalogResult>;
49
+ community(query?: {
50
+ page?: number;
51
+ pageSize?: number;
52
+ sort?: string;
53
+ search?: string;
54
+ }): Promise<RendererPetCommunityResult>;
55
+ preview(id: string): Promise<RendererPetPreviewResult>;
56
+ install(params: RendererPetInstallParams): Promise<RendererPetInstallResult>;
57
+ uninstall(id: string): Promise<{ id: string; removed: boolean }>;
58
+ }
59
+
60
+ export function createRendererPetsClient(
61
+ send: (method: string, params: unknown) => Promise<unknown>,
62
+ ): RendererPetsClient {
63
+ return {
64
+ catalog: async () => (await send("codexhost/pets/catalog", {})) as RendererPetCatalogResult,
65
+ community: async (query = {}) =>
66
+ (await send("codexhost/pets/community", query)) as RendererPetCommunityResult,
67
+ preview: async (id: string) =>
68
+ (await send("codexhost/pets/preview", { id })) as RendererPetPreviewResult,
69
+ install: async (params: RendererPetInstallParams) =>
70
+ (await send("codexhost/pets/install", params)) as RendererPetInstallResult,
71
+ uninstall: async (id: string) =>
72
+ (await send("codexhost/pets/uninstall", { id })) as { id: string; removed: boolean },
73
+ };
74
+ }
@@ -68,11 +68,13 @@ button {
68
68
  }
69
69
 
70
70
  .codexhost-settings-dialog {
71
- width: min(1120px, calc(100vw - 32px));
72
- height: min(780px, calc(100vh - 32px));
71
+ width: min(1120px, calc(100vw - 48px));
72
+ height: min(780px, calc(100vh - 88px));
73
73
  max-width: none;
74
- max-height: none;
75
- margin: auto;
74
+ max-height: calc(100vh - 88px);
75
+ margin-top: max(56px, calc((100vh - 780px) / 2));
76
+ margin-inline: auto;
77
+ margin-bottom: auto;
76
78
  padding: 0;
77
79
  overflow: hidden;
78
80
  color: var(--settings-text);
@@ -122,9 +124,9 @@ button {
122
124
  display: flex;
123
125
  align-items: center;
124
126
  min-width: 0;
125
- height: 48px;
127
+ height: 50px;
126
128
  flex: none;
127
- padding: 0 12px;
129
+ padding: 0 16px;
128
130
  border-bottom: 1px solid var(--settings-border);
129
131
  }
130
132
 
@@ -271,8 +273,8 @@ button {
271
273
  display: inline-flex;
272
274
  align-items: center;
273
275
  justify-content: center;
274
- width: 28px;
275
- height: 28px;
276
+ width: 32px;
277
+ height: 32px;
276
278
  flex: none;
277
279
  padding: 0;
278
280
  color: var(--settings-muted);
@@ -280,6 +282,7 @@ button {
280
282
  border: 0;
281
283
  border-radius: 8px;
282
284
  cursor: pointer;
285
+ transition: background-color 0.15s ease, color 0.15s ease, transform 0.08s ease;
283
286
  }
284
287
 
285
288
  .settings-icon-button:hover {
@@ -287,6 +290,22 @@ button {
287
290
  background: var(--settings-hover);
288
291
  }
289
292
 
293
+ .settings-icon-button:active {
294
+ background: var(--settings-active);
295
+ transform: scale(0.95);
296
+ }
297
+
298
+ .settings-header-actions .settings-icon-button {
299
+ width: 34px;
300
+ height: 34px;
301
+ border-radius: 8px;
302
+ }
303
+
304
+ .settings-header-actions .settings-icon-button:hover {
305
+ color: var(--settings-text);
306
+ background: var(--settings-hover);
307
+ }
308
+
290
309
  .settings-icon-button:focus-visible,
291
310
  .settings-nav-button:focus-visible {
292
311
  outline: 2px solid var(--settings-focus);
@@ -1,6 +1,7 @@
1
1
  import settingsCss from "./shell.css";
2
- import accountsCss from "./accounts.css";
3
- import skinMarketCss from "./skin-market.css";
2
+ import accountsCss from "./accounts.css";
3
+ import skinMarketCss from "./skin-market.css";
4
+ import petMarketCss from "./pet-market.css";
4
5
  import {
5
6
  RendererSettingsNavigationState,
6
7
  RendererSettingsPageScope,
@@ -69,7 +70,7 @@ export function mountRendererSettingsShell(
69
70
  root.style.colorScheme = RENDERER_SETTINGS_COLOR_SCHEME;
70
71
  const shadow = root.attachShadow({ mode: "open" });
71
72
  const style = ownerDocument.createElement("style");
72
- style.textContent = `${settingsCss}\n${accountsCss}\n${skinMarketCss}`;
73
+ style.textContent = `${settingsCss}\n${accountsCss}\n${skinMarketCss}\n${petMarketCss}`;
73
74
 
74
75
  const dialog = ownerDocument.createElement("dialog");
75
76
  dialog.className = "codexhost-settings-dialog";