@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
@@ -31,10 +31,11 @@ import Search from "lucide/dist/esm/icons/search.mjs";
31
31
  import CircleHelp from "lucide/dist/esm/icons/circle-question-mark.mjs";
32
32
  import X from "lucide/dist/esm/icons/x.mjs";
33
33
  import Users from "lucide/dist/esm/icons/users.mjs";
34
- import Plus from "lucide/dist/esm/icons/plus.mjs";
35
- import Database from 'lucide/dist/esm/icons/database.mjs';
36
- import Palette from "lucide/dist/esm/icons/palette.mjs";
37
- import ShieldCheck from "lucide/dist/esm/icons/shield-check.mjs";
34
+ import Plus from "lucide/dist/esm/icons/plus.mjs";
35
+ import Database from 'lucide/dist/esm/icons/database.mjs';
36
+ import Palette from "lucide/dist/esm/icons/palette.mjs";
37
+ import ShieldCheck from "lucide/dist/esm/icons/shield-check.mjs";
38
+ import PawPrint from "lucide/dist/esm/icons/paw-print.mjs";
38
39
  import harnessMixLogoUrl from "../../../../assets/brand-harness-mix.png";
39
40
 
40
41
  export const RENDERER_SETTINGS_ICON_NAMES = [
@@ -70,10 +71,11 @@ export const RENDERER_SETTINGS_ICON_NAMES = [
70
71
  "trash",
71
72
  "terminal",
72
73
  "search",
73
- "help",
74
- "palette",
75
- "shield",
76
- "storage",
74
+ "help",
75
+ "palette",
76
+ "shield",
77
+ "storage",
78
+ "pets",
77
79
  ] as const;
78
80
 
79
81
  export type RendererSettingsIconName = (typeof RENDERER_SETTINGS_ICON_NAMES)[number];
@@ -111,10 +113,11 @@ const iconNodes = {
111
113
  trash: Trash,
112
114
  terminal: Terminal,
113
115
  search: Search,
114
- help: CircleHelp,
115
- palette: Palette,
116
- shield: ShieldCheck,
117
- storage: Database,
116
+ help: CircleHelp,
117
+ palette: Palette,
118
+ shield: ShieldCheck,
119
+ storage: Database,
120
+ pets: PawPrint,
118
121
  } satisfies Record<RendererSettingsIconName, IconNode>;
119
122
 
120
123
  export function isRendererSettingsIconName(value: string): value is RendererSettingsIconName {
@@ -516,6 +516,7 @@ const ENGLISH_MESSAGES: RendererSettingsMessages = Object.freeze({
516
516
  "session-import": "Session Import",
517
517
  storage: "Storage & verification",
518
518
  skins: "Skins",
519
+ pets: "Pets",
519
520
  updates: "Updates",
520
521
  about: "About",
521
522
  }),
@@ -775,6 +776,7 @@ const CHINESE_MESSAGES: RendererSettingsMessages = Object.freeze({
775
776
  "session-import": "会话导入",
776
777
  storage: "存储与验证",
777
778
  skins: "皮肤",
779
+ pets: "桌宠",
778
780
  updates: "更新",
779
781
  about: "关于",
780
782
  }),
@@ -26,12 +26,14 @@ import {
26
26
  type RendererSessionImportClient,
27
27
  type RendererImportedThreadOpener,
28
28
  } from "./session-import-page.js";
29
- import { createReleaseNotesElement } from "./release-notes.js";
30
- import { createMcpSettingsPage, createSkillsSettingsPage } from './integrations-page.js';
31
- import type { RendererIntegrationsClient } from '../renderer-integrations-client.js';
32
- import { createAccountsSettingsPage, type RendererCodexAccountClient } from "./accounts-page.js";
33
- import { createStorageSettingsPage, type RendererStorageClient } from './storage-page.js';
34
- import { createSkinSettingsPage } from "./skin-market.js";
29
+ import { createReleaseNotesElement } from "./release-notes.js";
30
+ import { createMcpSettingsPage, createSkillsSettingsPage } from './integrations-page.js';
31
+ import type { RendererIntegrationsClient } from '../renderer-integrations-client.js';
32
+ import { createAccountsSettingsPage, type RendererCodexAccountClient } from "./accounts-page.js";
33
+ import { createStorageSettingsPage, type RendererStorageClient } from './storage-page.js';
34
+ import { createSkinSettingsPage } from "./skin-market.js";
35
+ import { createPetSettingsPage } from "./pet-market.js";
36
+ import type { RendererPetsClient } from "./pets-client.js";
35
37
 
36
38
  export type {
37
39
  RendererConnectionAgentSnapshot,
@@ -75,24 +77,25 @@ function windowsInstallerDownloadUrl(window: Window | null | undefined, version:
75
77
 
76
78
  export const DEFAULT_RENDERER_SETTINGS_PAGE_IDS = [
77
79
  "connections",
78
- "accounts",
79
- "mcp",
80
- "skills",
81
- "session-import",
82
- "storage",
83
- "skins",
84
- "updates",
80
+ "accounts",
81
+ "mcp",
82
+ "skills",
83
+ "session-import",
84
+ "storage",
85
+ "skins",
86
+ "pets",
87
+ "updates",
85
88
  "about",
86
89
  ] as const;
87
90
 
88
91
  export type DefaultRendererSettingsPageId = (typeof DEFAULT_RENDERER_SETTINGS_PAGE_IDS)[number];
89
92
 
90
- export interface RendererUpdateClient {
91
- checkUpdate(): Promise<UpdateCheckResult>;
92
- startUpdate(): Promise<UpdateStartResult>;
93
- readUpdateStatus(): Promise<UpdateStatusResult>;
94
- readCurrentVersion?(): Promise<{ version: string }>;
95
- }
93
+ export interface RendererUpdateClient {
94
+ checkUpdate(): Promise<UpdateCheckResult>;
95
+ startUpdate(): Promise<UpdateStartResult>;
96
+ readUpdateStatus(): Promise<UpdateStatusResult>;
97
+ readCurrentVersion?(): Promise<{ version: string }>;
98
+ }
96
99
 
97
100
  function panelIconName(view: string): RendererSettingsIconName {
98
101
  if (view === "failed" || view === "error") return "alert";
@@ -163,10 +166,10 @@ function formatUpdateBytes(value: number): string {
163
166
  return `${scaled.toFixed(scaled >= 10 ? 0 : 1)} ${unit}`;
164
167
  }
165
168
 
166
- export function aboutPage(
167
- messages: RendererSettingsMessages,
168
- getClient: () => RendererUpdateClient | null = () => null,
169
- ): RendererSettingsPageDefinition {
169
+ export function aboutPage(
170
+ messages: RendererSettingsMessages,
171
+ getClient: () => RendererUpdateClient | null = () => null,
172
+ ): RendererSettingsPageDefinition {
170
173
  return Object.freeze({
171
174
  id: "about",
172
175
  label: messages.pageLabels.about,
@@ -189,7 +192,7 @@ export function aboutPage(
189
192
  versionRow.className = "settings-about-version-row";
190
193
  const versionBadge = document.createElement("span");
191
194
  versionBadge.className = "settings-about-version-badge";
192
- versionBadge.textContent = "v…";
195
+ versionBadge.textContent = "v…";
193
196
  const checkUpdateBtn = document.createElement("button");
194
197
  checkUpdateBtn.type = "button";
195
198
  checkUpdateBtn.className = "settings-command-button settings-command-button--secondary";
@@ -229,27 +232,27 @@ export function aboutPage(
229
232
  repositoryUrl,
230
233
  );
231
234
  repositorySection.append(openSource, repository);
232
- panel.append(product, tagline, versionRow, introduction, starCallout, repositorySection);
233
- context.content.append(heading, panel);
234
- const client = getClient();
235
- const readVersion = client?.readCurrentVersion
236
- ? () => client.readCurrentVersion!()
237
- : client
238
- ? () => client.checkUpdate().then(({ currentVersion }) => ({ version: currentVersion }))
239
- : null;
240
- if (readVersion) {
241
- void context.runLatest(readVersion, {
242
- success(result) {
243
- versionBadge.textContent = `v${result.version}`;
244
- },
245
- failure() {
246
- versionBadge.textContent = "v—";
247
- },
248
- });
249
- } else {
250
- versionBadge.textContent = "v—";
251
- }
252
- return undefined;
235
+ panel.append(product, tagline, versionRow, introduction, starCallout, repositorySection);
236
+ context.content.append(heading, panel);
237
+ const client = getClient();
238
+ const readVersion = client?.readCurrentVersion
239
+ ? () => client.readCurrentVersion!()
240
+ : client
241
+ ? () => client.checkUpdate().then(({ currentVersion }) => ({ version: currentVersion }))
242
+ : null;
243
+ if (readVersion) {
244
+ void context.runLatest(readVersion, {
245
+ success(result) {
246
+ versionBadge.textContent = `v${result.version}`;
247
+ },
248
+ failure() {
249
+ versionBadge.textContent = "v—";
250
+ },
251
+ });
252
+ } else {
253
+ versionBadge.textContent = "v—";
254
+ }
255
+ return undefined;
253
256
  },
254
257
  });
255
258
  }
@@ -627,21 +630,23 @@ export function createDefaultRendererSettingsPages(
627
630
  getDiagnostics: () => RendererConnectionDiagnostics | null = () => null,
628
631
  getAccountClient: () => RendererCodexAccountClient | null = () => null,
629
632
  getSessionImportClient: () => RendererSessionImportClient | null = () => null,
630
- openImportedThread: RendererImportedThreadOpener = () =>
631
- Promise.reject(new Error("Imported Thread navigation is unavailable")),
632
- getIntegrationsClient: () => RendererIntegrationsClient | null = () => null,
633
- getStorageClient: () => RendererStorageClient | null = () => null,
634
- ): readonly RendererSettingsPageDefinition[] {
633
+ openImportedThread: RendererImportedThreadOpener = () =>
634
+ Promise.reject(new Error("Imported Thread navigation is unavailable")),
635
+ getIntegrationsClient: () => RendererIntegrationsClient | null = () => null,
636
+ getStorageClient: () => RendererStorageClient | null = () => null,
637
+ getPetClient: () => RendererPetsClient | null = () => null,
638
+ ): readonly RendererSettingsPageDefinition[] {
635
639
  return Object.freeze([
636
640
  createConnectionsSettingsPage(messages, getDiagnostics),
637
- createAccountsSettingsPage(messages, getAccountClient),
638
- createMcpSettingsPage(messages, getIntegrationsClient),
639
- createSkillsSettingsPage(messages, getIntegrationsClient),
640
- createSessionImportSettingsPage(messages, getSessionImportClient, openImportedThread),
641
- createStorageSettingsPage(messages, getStorageClient),
642
- createSkinSettingsPage(messages),
643
- updatesPage(messages, getUpdateClient),
644
- aboutPage(messages, getUpdateClient),
641
+ createAccountsSettingsPage(messages, getAccountClient),
642
+ createMcpSettingsPage(messages, getIntegrationsClient),
643
+ createSkillsSettingsPage(messages, getIntegrationsClient),
644
+ createSessionImportSettingsPage(messages, getSessionImportClient, openImportedThread),
645
+ createStorageSettingsPage(messages, getStorageClient),
646
+ createSkinSettingsPage(messages),
647
+ createPetSettingsPage(messages, getPetClient),
648
+ updatesPage(messages, getUpdateClient),
649
+ aboutPage(messages, getUpdateClient),
645
650
  ]);
646
651
  }
647
652
 
@@ -651,9 +656,10 @@ export function createDefaultRendererSettingsRegistry(
651
656
  getDiagnostics: () => RendererConnectionDiagnostics | null = () => null,
652
657
  getAccountClient: () => RendererCodexAccountClient | null = () => null,
653
658
  getSessionImportClient: () => RendererSessionImportClient | null = () => null,
654
- openImportedThread?: RendererImportedThreadOpener,
655
- getIntegrationsClient?: () => RendererIntegrationsClient | null,
656
- getStorageClient?: () => RendererStorageClient | null,
659
+ openImportedThread?: RendererImportedThreadOpener,
660
+ getIntegrationsClient?: () => RendererIntegrationsClient | null,
661
+ getStorageClient?: () => RendererStorageClient | null,
662
+ getPetClient?: () => RendererPetsClient | null,
657
663
  ): RendererSettingsPageRegistry {
658
664
  return createRendererSettingsPageRegistry(
659
665
  createDefaultRendererSettingsPages(
@@ -662,12 +668,14 @@ export function createDefaultRendererSettingsRegistry(
662
668
  getDiagnostics,
663
669
  getAccountClient,
664
670
  getSessionImportClient,
665
- openImportedThread,
666
- getIntegrationsClient,
667
- getStorageClient,
671
+ openImportedThread,
672
+ getIntegrationsClient,
673
+ getStorageClient,
674
+ getPetClient,
668
675
  ),
669
676
  );
670
677
  }
671
678
 
672
- export type { RendererCodexAccountClient } from "./accounts-page.js";
673
- export type { RendererStorageClient, RendererStorageInspection } from './storage-page.js';
679
+ export type { RendererCodexAccountClient } from "./accounts-page.js";
680
+ export type { RendererStorageClient, RendererStorageInspection } from './storage-page.js';
681
+ export type { RendererPetsClient } from "./pets-client.js";
@@ -0,0 +1,310 @@
1
+ .pet-market__intro {
2
+ margin: 0 0 16px;
3
+ font-size: 13px;
4
+ line-height: 1.5;
5
+ color: var(--text-secondary, rgba(255, 255, 255, 0.65));
6
+ }
7
+
8
+ .pet-safety-note {
9
+ display: flex;
10
+ align-items: flex-start;
11
+ gap: 10px;
12
+ padding: 10px 14px;
13
+ margin-bottom: 20px;
14
+ border-radius: 8px;
15
+ background: var(--bg-secondary, rgba(255, 255, 255, 0.04));
16
+ border: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.08));
17
+ font-size: 12px;
18
+ line-height: 1.4;
19
+ color: var(--text-secondary, rgba(255, 255, 255, 0.7));
20
+ }
21
+
22
+ .pet-safety-note strong {
23
+ display: block;
24
+ font-size: 12px;
25
+ font-weight: 600;
26
+ color: var(--text-primary, #ffffff);
27
+ margin-bottom: 2px;
28
+ }
29
+
30
+ .pet-safety-note svg {
31
+ flex-shrink: 0;
32
+ color: var(--accent, #10a37f);
33
+ margin-top: 1px;
34
+ }
35
+
36
+ .pet-market__toolbar {
37
+ display: flex;
38
+ flex-wrap: wrap;
39
+ align-items: center;
40
+ justify-content: space-between;
41
+ gap: 12px;
42
+ margin-bottom: 18px;
43
+ }
44
+
45
+ .pet-market__tabs {
46
+ display: flex;
47
+ align-items: center;
48
+ gap: 6px;
49
+ background: var(--bg-secondary, rgba(255, 255, 255, 0.04));
50
+ padding: 3px;
51
+ border-radius: 8px;
52
+ border: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.08));
53
+ }
54
+
55
+ .pet-market__tab {
56
+ appearance: none;
57
+ background: transparent;
58
+ border: none;
59
+ color: var(--text-secondary, rgba(255, 255, 255, 0.6));
60
+ padding: 5px 12px;
61
+ border-radius: 6px;
62
+ font-size: 12px;
63
+ font-weight: 500;
64
+ cursor: pointer;
65
+ transition: background 0.15s, color 0.15s;
66
+ }
67
+
68
+ .pet-market__tab:hover {
69
+ color: var(--text-primary, #ffffff);
70
+ background: rgba(255, 255, 255, 0.05);
71
+ }
72
+
73
+ .pet-market__tab[data-active="true"] {
74
+ background: var(--bg-tertiary, rgba(255, 255, 255, 0.12));
75
+ color: var(--text-primary, #ffffff);
76
+ font-weight: 600;
77
+ }
78
+
79
+ .pet-market__search-wrap {
80
+ position: relative;
81
+ min-width: 200px;
82
+ flex: 1;
83
+ max-width: 280px;
84
+ }
85
+
86
+ .pet-market__search-wrap svg {
87
+ position: absolute;
88
+ left: 10px;
89
+ top: 50%;
90
+ transform: translateY(-50%);
91
+ pointer-events: none;
92
+ color: var(--text-tertiary, rgba(255, 255, 255, 0.4));
93
+ }
94
+
95
+ .pet-market__search {
96
+ width: 100%;
97
+ padding: 6px 12px 6px 32px;
98
+ border-radius: 6px;
99
+ background: var(--bg-secondary, rgba(255, 255, 255, 0.05));
100
+ border: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.1));
101
+ color: var(--text-primary, #ffffff);
102
+ font-size: 12px;
103
+ outline: none;
104
+ transition: border-color 0.15s;
105
+ }
106
+
107
+ .pet-market__search:focus {
108
+ border-color: var(--accent, #10a37f);
109
+ }
110
+
111
+ .pet-market-grid {
112
+ display: grid;
113
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
114
+ gap: 16px;
115
+ }
116
+
117
+ .pet-card {
118
+ display: flex;
119
+ flex-direction: column;
120
+ background: var(--bg-secondary, rgba(255, 255, 255, 0.03));
121
+ border: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.08));
122
+ border-radius: 10px;
123
+ overflow: hidden;
124
+ transition: border-color 0.2s, box-shadow 0.2s;
125
+ }
126
+
127
+ .pet-card:hover {
128
+ border-color: var(--border-strong, rgba(255, 255, 255, 0.18));
129
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
130
+ }
131
+
132
+ .pet-card[data-installed="true"] {
133
+ border-color: rgba(16, 163, 127, 0.35);
134
+ }
135
+
136
+ .pet-card__stage {
137
+ position: relative;
138
+ display: flex;
139
+ align-items: center;
140
+ justify-content: center;
141
+ height: 140px;
142
+ background: radial-gradient(circle at center, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.01) 70%);
143
+ border-bottom: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.06));
144
+ overflow: hidden;
145
+ user-select: none;
146
+ }
147
+
148
+ .pet-card__sprite {
149
+ width: 96px;
150
+ height: 104px;
151
+ background-repeat: no-repeat;
152
+ image-rendering: pixelated;
153
+ image-rendering: -webkit-optimize-contrast;
154
+ transition: transform 0.2s;
155
+ }
156
+
157
+ .pet-card:hover .pet-card__sprite {
158
+ transform: scale(1.08);
159
+ }
160
+
161
+ .pet-card__body {
162
+ display: flex;
163
+ flex-direction: column;
164
+ flex: 1;
165
+ padding: 14px;
166
+ }
167
+
168
+ .pet-card__header {
169
+ display: flex;
170
+ align-items: flex-start;
171
+ justify-content: space-between;
172
+ gap: 8px;
173
+ margin-bottom: 6px;
174
+ }
175
+
176
+ .pet-card__title {
177
+ font-size: 14px;
178
+ font-weight: 600;
179
+ color: var(--text-primary, #ffffff);
180
+ }
181
+
182
+ .pet-card__badges {
183
+ display: flex;
184
+ align-items: center;
185
+ gap: 4px;
186
+ flex-shrink: 0;
187
+ }
188
+
189
+ .pet-card__badge {
190
+ display: inline-flex;
191
+ align-items: center;
192
+ gap: 3px;
193
+ font-size: 10.5px;
194
+ padding: 2px 6px;
195
+ border-radius: 4px;
196
+ font-weight: 500;
197
+ }
198
+
199
+ .pet-card__badge--official {
200
+ background: rgba(59, 130, 246, 0.15);
201
+ color: #60a5fa;
202
+ border: 1px solid rgba(59, 130, 246, 0.3);
203
+ }
204
+
205
+ .pet-card__badge--community {
206
+ background: rgba(168, 85, 247, 0.15);
207
+ color: #c084fc;
208
+ border: 1px solid rgba(168, 85, 247, 0.3);
209
+ }
210
+
211
+ .pet-card__badge--installed {
212
+ background: rgba(16, 163, 127, 0.15);
213
+ color: #10a37f;
214
+ border: 1px solid rgba(16, 163, 127, 0.3);
215
+ }
216
+
217
+ .pet-card__description {
218
+ margin: 0 0 14px;
219
+ font-size: 12px;
220
+ line-height: 1.45;
221
+ color: var(--text-secondary, rgba(255, 255, 255, 0.6));
222
+ flex: 1;
223
+ display: -webkit-box;
224
+ -webkit-line-clamp: 3;
225
+ -webkit-box-orient: vertical;
226
+ overflow: hidden;
227
+ }
228
+
229
+ .pet-card__footer {
230
+ display: flex;
231
+ align-items: center;
232
+ justify-content: space-between;
233
+ gap: 8px;
234
+ margin-top: auto;
235
+ padding-top: 10px;
236
+ border-top: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.05));
237
+ }
238
+
239
+ .pet-card__dir-hint {
240
+ font-size: 11px;
241
+ color: var(--text-tertiary, rgba(255, 255, 255, 0.4));
242
+ white-space: nowrap;
243
+ overflow: hidden;
244
+ text-overflow: ellipsis;
245
+ }
246
+
247
+ .pet-card__actions {
248
+ display: flex;
249
+ align-items: center;
250
+ gap: 6px;
251
+ margin-left: auto;
252
+ }
253
+
254
+ .pet-btn {
255
+ display: inline-flex;
256
+ align-items: center;
257
+ justify-content: center;
258
+ gap: 6px;
259
+ font-size: 12px;
260
+ font-weight: 500;
261
+ padding: 5px 12px;
262
+ border-radius: 6px;
263
+ border: 1px solid transparent;
264
+ cursor: pointer;
265
+ transition: all 0.15s;
266
+ }
267
+
268
+ .pet-btn--primary {
269
+ background: var(--accent, #10a37f);
270
+ color: #ffffff;
271
+ }
272
+
273
+ .pet-btn--primary:hover:not(:disabled) {
274
+ filter: brightness(1.1);
275
+ }
276
+
277
+ .pet-btn--secondary {
278
+ background: var(--bg-tertiary, rgba(255, 255, 255, 0.08));
279
+ border-color: var(--border-subtle, rgba(255, 255, 255, 0.1));
280
+ color: var(--text-secondary, rgba(255, 255, 255, 0.75));
281
+ }
282
+
283
+ .pet-btn--secondary:hover:not(:disabled) {
284
+ background: rgba(255, 255, 255, 0.12);
285
+ color: var(--text-primary, #ffffff);
286
+ }
287
+
288
+ .pet-btn--danger {
289
+ background: transparent;
290
+ border-color: rgba(239, 68, 68, 0.25);
291
+ color: #f87171;
292
+ }
293
+
294
+ .pet-btn--danger:hover:not(:disabled) {
295
+ background: rgba(239, 68, 68, 0.12);
296
+ border-color: rgba(239, 68, 68, 0.5);
297
+ }
298
+
299
+ .pet-btn:disabled {
300
+ opacity: 0.5;
301
+ cursor: not-allowed;
302
+ }
303
+
304
+ .pet-market__empty {
305
+ grid-column: 1 / -1;
306
+ text-align: center;
307
+ padding: 40px 20px;
308
+ color: var(--text-tertiary, rgba(255, 255, 255, 0.45));
309
+ font-size: 13px;
310
+ }