@hayasaka7/haya-pet 0.2.8 → 0.3.0
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/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ All notable changes to HAYA Pet are documented here. This project adheres to
|
|
|
7
7
|
> 0.2.0 npm publish; they are listed under 0.2.1, which is the first version that
|
|
8
8
|
> ships them.
|
|
9
9
|
|
|
10
|
+
## [0.3.0]
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **A fresh install now shows a real pet.** The package has always shipped a
|
|
14
|
+
ready-to-use pet (`assets/fallback-pet`), but discovery only scanned the
|
|
15
|
+
user's pet folders (`~/.codex/pets`, `~/.haya-pet/pets`), so a new user with
|
|
16
|
+
no pets got a blue "dev placeholder" box and an empty pet list instead. The
|
|
17
|
+
bundled pet is now composed into discovery as a last resort — appended after
|
|
18
|
+
any of the user's own pets (which still win and stay the default) and deduped
|
|
19
|
+
by id — so the overlay always renders a real character out of the box.
|
|
20
|
+
|
|
10
21
|
## [0.2.8]
|
|
11
22
|
|
|
12
23
|
### Fixed
|
package/apps/cli/src/haya-pet.js
CHANGED
|
@@ -14,7 +14,7 @@ import { watchCodexGuardianReviews as defaultWatchCodexGuardianReviews } from ".
|
|
|
14
14
|
import { ensureCompanionConnection } from "../../../packages/cli-core/src/companion-launcher.js";
|
|
15
15
|
import { createIpcClient as defaultCreateIpcClient } from "../../../packages/daemon-core/src/ipc-server.js";
|
|
16
16
|
import { getDefaultPaths } from "../../../packages/platform-core/src/paths.js";
|
|
17
|
-
import {
|
|
17
|
+
import { discoverPetsWithFallback as defaultDiscoverPets } from "../../../packages/pet-core/src/discovery.js";
|
|
18
18
|
import { createStateFile as defaultCreateStateFile } from "../../../packages/app-state/src/state-file.js";
|
|
19
19
|
import { getSelectedPetId, setSelectedPet, getHooksEnabled, setHooksEnabled } from "../../../packages/app-state/src/state.js";
|
|
20
20
|
import { checkForUpdate, UPDATE_COMMAND } from "../../../packages/app-state/src/update-check.js";
|
|
@@ -18,7 +18,7 @@ import { resolveSavedPosition } from "./display-manager.js";
|
|
|
18
18
|
import { getPetScale, setPetScale, setSelectedPet, updateGlobalPetPosition } from "./position-store.js";
|
|
19
19
|
import { buildTrayMenu, buildTrayTooltip } from "./tray-menu.js";
|
|
20
20
|
import { createStateFile } from "./state-file.js";
|
|
21
|
-
import {
|
|
21
|
+
import { discoverPetsWithFallback } from "./pet-loader.js";
|
|
22
22
|
import { checkForUpdate, UPDATE_PAGE_URL } from "../../../../packages/app-state/src/update-check.js";
|
|
23
23
|
|
|
24
24
|
const STALE_SWEEP_INTERVAL_MS = 10_000;
|
|
@@ -67,7 +67,7 @@ if (!app.requestSingleInstanceLock()) {
|
|
|
67
67
|
async function bootstrap() {
|
|
68
68
|
positionState = await stateFile.load();
|
|
69
69
|
petScale = clampScale(getPetScale(positionState));
|
|
70
|
-
pets = await
|
|
70
|
+
pets = await discoverPetsWithFallback(paths.petSearchPaths);
|
|
71
71
|
|
|
72
72
|
// Clients fire no event at the moment the user ACCEPTS a permission prompt
|
|
73
73
|
// (only denial/finish are observable), so a waiting_approval session would
|
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
// Re-exported from pet-core so the companion and CLI share one discovery path.
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
discoverPets,
|
|
4
|
+
discoverPetsWithFallback,
|
|
5
|
+
loadPetFromDir
|
|
6
|
+
} from "../../../../packages/pet-core/src/discovery.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readdir as fsReaddir, readFile as fsReadFile, stat as fsStat } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { pathToFileURL } from "node:url";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
4
|
import { parsePetManifest } from "./manifest.js";
|
|
5
5
|
|
|
6
6
|
// Scans pet search paths for Codex-compatible pets. Each pet is a directory
|
|
@@ -28,6 +28,33 @@ export async function discoverPets(searchPaths = [], deps = {}) {
|
|
|
28
28
|
return pets;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// discoverPets only scans the user's pet folders (~/.codex/pets, ~/.haya-pet/pets),
|
|
32
|
+
// so a fresh install with no pets renders the dev placeholder instead of a real
|
|
33
|
+
// character. The package ships a ready-to-use pet; this composes it in as a
|
|
34
|
+
// last resort — appended after the user's pets so a real install still wins for
|
|
35
|
+
// the default selection, and deduped by id so a user who copied it in isn't
|
|
36
|
+
// shown two. The bundled directory is injectable so this stays testable.
|
|
37
|
+
export async function discoverPetsWithFallback(searchPaths = [], deps = {}) {
|
|
38
|
+
const pets = await discoverPets(searchPaths, deps);
|
|
39
|
+
const fallbackDir = deps.fallbackPetDir ?? getBundledFallbackPetDir();
|
|
40
|
+
const fallback = await loadPetFromDir(fallbackDir, deps);
|
|
41
|
+
|
|
42
|
+
if (!fallback || pets.some((pet) => pet.manifest.id === fallback.manifest.id)) {
|
|
43
|
+
return pets;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return [...pets, fallback];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Absolute path to the bundled fallback pet, resolved relative to this module so
|
|
50
|
+
// it works the same for global installs, npm link, and source checkouts. The
|
|
51
|
+
// asset lives at the package root (assets/fallback-pet); this file sits three
|
|
52
|
+
// levels below it (packages/pet-core/src).
|
|
53
|
+
export function getBundledFallbackPetDir() {
|
|
54
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
55
|
+
return join(here, "..", "..", "..", "assets", "fallback-pet");
|
|
56
|
+
}
|
|
57
|
+
|
|
31
58
|
export async function loadPetFromDir(petDir, deps = {}) {
|
|
32
59
|
const { readFile, stat } = resolveFs(deps);
|
|
33
60
|
const manifestPath = join(petDir, "pet.json");
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { test } from "../../../test/harness.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
discoverPets,
|
|
6
|
+
discoverPetsWithFallback,
|
|
7
|
+
getBundledFallbackPetDir,
|
|
8
|
+
loadPetFromDir
|
|
9
|
+
} from "../src/discovery.js";
|
|
5
10
|
|
|
6
11
|
function manifest(id, name) {
|
|
7
12
|
return JSON.stringify({ id, name, spritesheet: "spritesheet.webp" });
|
|
@@ -91,3 +96,60 @@ test("returns undefined for an invalid manifest", async () => {
|
|
|
91
96
|
test("tolerates missing search directories", async () => {
|
|
92
97
|
assert.deepEqual(await discoverPets([join("does-not-exist")], fakeFs()), []);
|
|
93
98
|
});
|
|
99
|
+
|
|
100
|
+
test("falls back to the bundled pet when the user has no pets", async () => {
|
|
101
|
+
const fallbackDir = join("bundled", "fallback-pet");
|
|
102
|
+
const fs = fakeFs({
|
|
103
|
+
files: {
|
|
104
|
+
[join(fallbackDir, "pet.json")]: manifest("fallback-pet", "Fallback"),
|
|
105
|
+
[join(fallbackDir, "spritesheet.webp")]: "x"
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const pets = await discoverPetsWithFallback([join("empty")], { ...fs, fallbackPetDir: fallbackDir });
|
|
110
|
+
assert.equal(pets.length, 1);
|
|
111
|
+
assert.equal(pets[0].manifest.id, "fallback-pet");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("appends the bundled pet after the user's own pets", async () => {
|
|
115
|
+
const root = join("petsdir");
|
|
116
|
+
const fallbackDir = join("bundled", "fallback-pet");
|
|
117
|
+
const fs = fakeFs({
|
|
118
|
+
dirs: { [root]: ["cat"] },
|
|
119
|
+
files: {
|
|
120
|
+
[join(root, "cat", "pet.json")]: manifest("cat", "Cat"),
|
|
121
|
+
[join(root, "cat", "spritesheet.webp")]: "x",
|
|
122
|
+
[join(fallbackDir, "pet.json")]: manifest("fallback-pet", "Fallback"),
|
|
123
|
+
[join(fallbackDir, "spritesheet.webp")]: "x"
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const pets = await discoverPetsWithFallback([root], { ...fs, fallbackPetDir: fallbackDir });
|
|
128
|
+
assert.deepEqual(pets.map((pet) => pet.manifest.id), ["cat", "fallback-pet"]);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("does not duplicate the bundled pet when the user already installed it", async () => {
|
|
132
|
+
const root = join("petsdir");
|
|
133
|
+
const fallbackDir = join("bundled", "fallback-pet");
|
|
134
|
+
const fs = fakeFs({
|
|
135
|
+
dirs: { [root]: ["fallback-pet"] },
|
|
136
|
+
files: {
|
|
137
|
+
[join(root, "fallback-pet", "pet.json")]: manifest("fallback-pet", "User Copy"),
|
|
138
|
+
[join(root, "fallback-pet", "spritesheet.webp")]: "x",
|
|
139
|
+
[join(fallbackDir, "pet.json")]: manifest("fallback-pet", "Bundled"),
|
|
140
|
+
[join(fallbackDir, "spritesheet.webp")]: "x"
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const pets = await discoverPetsWithFallback([root], { ...fs, fallbackPetDir: fallbackDir });
|
|
145
|
+
assert.equal(pets.length, 1);
|
|
146
|
+
assert.equal(pets[0].manifest.name, "User Copy");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Guards both the package-relative path resolution and that the asset actually
|
|
150
|
+
// ships: if either breaks, a fresh install renders the dev placeholder again.
|
|
151
|
+
test("ships a bundled fallback pet that loads from disk", async () => {
|
|
152
|
+
const pet = await loadPetFromDir(getBundledFallbackPetDir());
|
|
153
|
+
assert.ok(pet, "bundled fallback pet should load from the shipped package");
|
|
154
|
+
assert.equal(pet.manifest.id, "fallback-pet");
|
|
155
|
+
});
|