hyperframes 0.8.66 → 0.8.68
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/dist/cli.js +1322 -667
- package/dist/fontLocalizeCli.js +407 -133
- package/dist/hyperframes-player.global.js +1 -1
- package/dist/renderSetupWorker.js +10625 -41829
- package/dist/skills/media-use/SKILL.md +10 -10
- package/dist/skills/media-use/references/meta.md +1 -1
- package/dist/skills/media-use/references/resolve.md +1 -1
- package/dist/skills/media-use/references/setup-providers.md +1 -1
- package/dist/skills/media-use/scripts/lib/coverage.test.mjs +5 -1
- package/dist/skills/media-use/scripts/lib/freeze.mjs +34 -24
- package/dist/skills/media-use/scripts/lib/freeze.test.mjs +93 -1
- package/dist/skills/media-use/scripts/lib/logo-provider.mjs +110 -89
- package/dist/skills/media-use/scripts/lib/logo-provider.test.mjs +204 -60
- package/dist/skills/media-use/scripts/lib/media-fetch.mjs +14 -0
- package/dist/skills/media-use/scripts/lib/media-fetch.test.mjs +29 -1
- package/dist/skills/media-use/scripts/lib/registry.mjs +6 -12
- package/dist/skills/media-use/scripts/lib/registry.test.mjs +1 -1
- package/dist/skills/media-use/scripts/lib/svg-sanitize.mjs +21 -0
- package/dist/studio/assets/{hyperframes-player-BRlwG-p9.js → hyperframes-player-RYzFlKye.js} +1 -1
- package/dist/studio/assets/index-BBOktYKC.css +1 -0
- package/dist/studio/assets/{index-D6Jy-2Zs.js → index-C8-wAsq3.js} +1 -1
- package/dist/studio/assets/{index-DJl2JKQj.js → index-CsnhFJIv.js} +1 -1
- package/dist/studio/assets/{index-BzGcmMBS.js → index-P0Rwat37.js} +200 -200
- package/dist/studio/index.css +147 -16
- package/dist/studio/index.css.map +1 -1
- package/dist/studio/index.html +2 -2
- package/dist/studio/index.js +4436 -4193
- package/dist/studio/index.js.map +1 -1
- package/dist/studio/styles.css +1 -1
- package/package.json +1 -1
- package/dist/studio/assets/index-BnsYeuxA.css +0 -1
|
@@ -4,12 +4,11 @@ import { readFileSync } from "node:fs";
|
|
|
4
4
|
import {
|
|
5
5
|
entityFrom,
|
|
6
6
|
titleMatches,
|
|
7
|
-
|
|
8
|
-
simpleIconSlugsFor,
|
|
7
|
+
thesvgMatch,
|
|
9
8
|
githubOrgFor,
|
|
10
9
|
faviconDomainFor,
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
thesvgSearch,
|
|
11
|
+
resetThesvgManifest,
|
|
13
12
|
githubAvatarSearch,
|
|
14
13
|
faviconSearch,
|
|
15
14
|
} from "./logo-provider.mjs";
|
|
@@ -27,16 +26,126 @@ test("titleMatches ignores case, spacing, punctuation — and rejects lookalikes
|
|
|
27
26
|
assert.ok(!titleMatches("Slackware", "slack"));
|
|
28
27
|
});
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
// A slice of the real theSVG manifest shape (src/data/icons.json).
|
|
30
|
+
const ICONS = [
|
|
31
|
+
{
|
|
32
|
+
slug: "nextdotjs",
|
|
33
|
+
title: "Next.js",
|
|
34
|
+
aliases: [],
|
|
35
|
+
collection: "brands",
|
|
36
|
+
license: "CC0-1.0",
|
|
37
|
+
variants: { default: "/icons/nextdotjs/default.svg", mono: "/icons/nextdotjs/mono.svg" },
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
slug: "slack-badge",
|
|
41
|
+
title: "Slack",
|
|
42
|
+
aliases: [],
|
|
43
|
+
collection: "auth-badges",
|
|
44
|
+
license: "CC0-1.0",
|
|
45
|
+
variants: { default: "/icons/slack-badge/default.svg" },
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
slug: "slack",
|
|
49
|
+
title: "Slack",
|
|
50
|
+
aliases: [],
|
|
51
|
+
collection: "brands",
|
|
52
|
+
license: "CC0-1.0",
|
|
53
|
+
variants: { default: "/icons/slack/default.svg", wordmark: "/icons/slack/wordmark.svg" },
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
slug: "aws",
|
|
57
|
+
title: "AWS",
|
|
58
|
+
aliases: ["Amazon Web Services"],
|
|
59
|
+
collection: "brands",
|
|
60
|
+
license: "MIT",
|
|
61
|
+
variants: { default: "/icons/aws/color.svg", mono: "/icons/aws/mono.svg" },
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
slug: "coca-cola",
|
|
65
|
+
title: "Coca-Cola",
|
|
66
|
+
aliases: [],
|
|
67
|
+
collection: "brands",
|
|
68
|
+
license: "CC0-1.0",
|
|
69
|
+
variants: { default: "/icons/coca-cola/default.svg" },
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
slug: "slackware",
|
|
73
|
+
title: "Slackware",
|
|
74
|
+
aliases: [],
|
|
75
|
+
collection: "brands",
|
|
76
|
+
license: "CC0-1.0",
|
|
77
|
+
variants: { default: "/icons/slackware/default.svg" },
|
|
78
|
+
},
|
|
79
|
+
// Mirrors the real manifest's Microsoft entries verbatim (theSVG's own
|
|
80
|
+
// "needs maintainer review" wording) — never a valid match.
|
|
81
|
+
{
|
|
82
|
+
slug: "power-bi",
|
|
83
|
+
title: "Power BI",
|
|
84
|
+
aliases: [],
|
|
85
|
+
collection: "brands",
|
|
86
|
+
license:
|
|
87
|
+
"Microsoft proprietary product icon; no express redistribution license supplied; maintainer review required",
|
|
88
|
+
variants: { default: "/icons/power-bi/default.svg" },
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
slug: "acme-unresolved",
|
|
92
|
+
title: "Acme Unresolved",
|
|
93
|
+
aliases: [],
|
|
94
|
+
collection: "brands",
|
|
95
|
+
license: "brand-use",
|
|
96
|
+
variants: { default: "/icons/acme-unresolved/default.svg" },
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
test("thesvgMatch finds entries by normalized slug, title, or alias", () => {
|
|
101
|
+
assert.equal(thesvgMatch(ICONS, "nextjs").slug, "nextdotjs", "title Next.js ≡ nextjs");
|
|
102
|
+
assert.equal(thesvgMatch(ICONS, "coca cola").slug, "coca-cola");
|
|
103
|
+
assert.equal(thesvgMatch(ICONS, "amazon web services").slug, "aws", "alias hit");
|
|
104
|
+
assert.equal(thesvgMatch(ICONS, "zzzbrand"), null);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("thesvgMatch prefers the brand mark over an auth badge with the same title", () => {
|
|
108
|
+
assert.equal(thesvgMatch(ICONS, "slack").slug, "slack");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("thesvgMatch never returns a lookalike", () => {
|
|
112
|
+
assert.equal(thesvgMatch(ICONS, "slackwa"), null);
|
|
113
|
+
assert.equal(thesvgMatch(ICONS, ""), null);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("thesvgMatch skips an entry outside theSVG's ten accepted SPDX licenses", () => {
|
|
117
|
+
assert.equal(
|
|
118
|
+
thesvgMatch(ICONS, "power bi"),
|
|
119
|
+
null,
|
|
120
|
+
"Microsoft's own 'needs maintainer review' text",
|
|
121
|
+
);
|
|
122
|
+
assert.equal(
|
|
123
|
+
thesvgMatch(ICONS, "acme unresolved"),
|
|
124
|
+
null,
|
|
125
|
+
"brand-use is not on the accepted list",
|
|
126
|
+
);
|
|
34
127
|
});
|
|
35
128
|
|
|
36
|
-
test("
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
129
|
+
test("thesvgMatch falls through to a lower-ranked accepted entry when the top match is unlicensed", () => {
|
|
130
|
+
const icons = [
|
|
131
|
+
{
|
|
132
|
+
slug: "acme",
|
|
133
|
+
title: "Acme",
|
|
134
|
+
aliases: [],
|
|
135
|
+
collection: "brands",
|
|
136
|
+
license: "Proprietary",
|
|
137
|
+
variants: { default: "/icons/acme/default.svg" },
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
slug: "acme-community",
|
|
141
|
+
title: "Acme",
|
|
142
|
+
aliases: [],
|
|
143
|
+
collection: "community",
|
|
144
|
+
license: "CC0-1.0",
|
|
145
|
+
variants: { default: "/icons/acme-community/default.svg" },
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
assert.equal(thesvgMatch(icons, "acme").slug, "acme-community");
|
|
40
149
|
});
|
|
41
150
|
|
|
42
151
|
test("github avatar tier never guesses an org", () => {
|
|
@@ -59,45 +168,59 @@ const json = (data) => new Response(JSON.stringify(data), { status: 200 });
|
|
|
59
168
|
const status = (code) => new Response(null, { status: code });
|
|
60
169
|
const bin = (n) => new Response(new Uint8Array(n), { status: 200 });
|
|
61
170
|
|
|
62
|
-
test("
|
|
63
|
-
|
|
64
|
-
|
|
171
|
+
test("thesvgSearch returns a pinned CDN url for the default variant", async (t) => {
|
|
172
|
+
resetThesvgManifest();
|
|
173
|
+
t.mock.method(globalThis, "fetch", async () => json(ICONS));
|
|
174
|
+
const res = await thesvgSearch("AWS logo", {});
|
|
175
|
+
assert.match(
|
|
176
|
+
res.url,
|
|
177
|
+
/^https:\/\/cdn\.jsdelivr\.net\/gh\/glincker\/thesvg@[0-9a-f]{40}\/public\/icons\/aws\/color\.svg$/,
|
|
65
178
|
);
|
|
66
|
-
const res = await svglSearch("Figma logo", {});
|
|
67
|
-
assert.equal(res.url, "https://svgl.app/library/figma.svg");
|
|
68
179
|
assert.equal(res.ext, ".svg");
|
|
69
|
-
assert.equal(res.metadata.provider, "
|
|
180
|
+
assert.equal(res.metadata.provider, "thesvg");
|
|
181
|
+
assert.equal(res.metadata.provenance.slug, "aws");
|
|
182
|
+
assert.deepEqual(res.metadata.provenance.variants, ["default", "mono"]);
|
|
70
183
|
});
|
|
71
184
|
|
|
72
|
-
test("
|
|
73
|
-
|
|
74
|
-
t.mock.method(globalThis, "fetch", async (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
});
|
|
80
|
-
const res = await svglSearch("nextjs logo", {});
|
|
81
|
-
assert.equal(res.metadata.provenance.query, "next.js", "hit came from the alias query");
|
|
82
|
-
assert.ok(seen.length >= 2, "raw query then alias");
|
|
185
|
+
test("thesvgSearch fetches the manifest once per process", async (t) => {
|
|
186
|
+
resetThesvgManifest();
|
|
187
|
+
const fetchMock = t.mock.method(globalThis, "fetch", async () => json(ICONS));
|
|
188
|
+
await thesvgSearch("slack logo", {});
|
|
189
|
+
await thesvgSearch("nextjs logo", {});
|
|
190
|
+
assert.equal(await thesvgSearch("zzzbrand logo", {}), null);
|
|
191
|
+
assert.equal(fetchMock.mock.callCount(), 1);
|
|
83
192
|
});
|
|
84
193
|
|
|
85
|
-
test("
|
|
194
|
+
test("thesvgSearch returns null when the network is down, then retries next call", async (t) => {
|
|
195
|
+
resetThesvgManifest();
|
|
196
|
+
let down = true;
|
|
86
197
|
t.mock.method(globalThis, "fetch", async () => {
|
|
87
|
-
throw new Error("network down");
|
|
198
|
+
if (down) throw new Error("network down");
|
|
199
|
+
return json(ICONS);
|
|
88
200
|
});
|
|
89
|
-
assert.equal(await
|
|
201
|
+
assert.equal(await thesvgSearch("figma logo", {}), null);
|
|
202
|
+
down = false;
|
|
203
|
+
assert.equal((await thesvgSearch("slack logo", {})).metadata.provenance.slug, "slack");
|
|
90
204
|
});
|
|
91
205
|
|
|
92
|
-
test("
|
|
93
|
-
|
|
94
|
-
t.mock.method(globalThis, "fetch", async (
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
206
|
+
test("thesvgSearch treats a non-array manifest as a miss", async (t) => {
|
|
207
|
+
resetThesvgManifest();
|
|
208
|
+
t.mock.method(globalThis, "fetch", async () => json({ error: "unexpected shape" }));
|
|
209
|
+
assert.equal(await thesvgSearch("slack logo", {}), null);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("thesvgSearch rejects a manifest body over the size cap instead of buffering it whole", async (t) => {
|
|
213
|
+
resetThesvgManifest();
|
|
214
|
+
t.mock.method(globalThis, "fetch", async () => ({
|
|
215
|
+
ok: true,
|
|
216
|
+
status: 200,
|
|
217
|
+
headers: { get: () => null },
|
|
218
|
+
// 4 × 6MB > the 20MB cap, no content-length header (matches jsDelivr).
|
|
219
|
+
body: (async function* () {
|
|
220
|
+
for (let i = 0; i < 4; i++) yield new Uint8Array(6 * 1024 * 1024);
|
|
221
|
+
})(),
|
|
222
|
+
}));
|
|
223
|
+
assert.equal(await thesvgSearch("slack logo", {}), null);
|
|
101
224
|
});
|
|
102
225
|
|
|
103
226
|
test("faviconSearch rejects DDG's sub-500B placeholder with null", async (t) => {
|
|
@@ -120,13 +243,24 @@ test("githubAvatarSearch never touches the network for an unmapped entity", asyn
|
|
|
120
243
|
assert.equal(fetchMock.mock.callCount(), 0);
|
|
121
244
|
});
|
|
122
245
|
|
|
246
|
+
test("the real logo cascade resolves a theSVG brand on tier 1", async (t) => {
|
|
247
|
+
resetThesvgManifest();
|
|
248
|
+
t.mock.method(globalThis, "fetch", async (url) => {
|
|
249
|
+
if (String(url).includes("glincker/thesvg")) return json(ICONS);
|
|
250
|
+
throw new Error(`unexpected fetch: ${url}`);
|
|
251
|
+
});
|
|
252
|
+
const res = await runProviders(getProviders("logo"), "search", "Next.js logo", {});
|
|
253
|
+
assert.equal(res.metadata.provider, "thesvg");
|
|
254
|
+
assert.ok(res.url.endsWith("/public/icons/nextdotjs/default.svg"));
|
|
255
|
+
});
|
|
256
|
+
|
|
123
257
|
test("the real logo cascade falls through tier by tier to the first hit", async (t) => {
|
|
258
|
+
resetThesvgManifest();
|
|
124
259
|
t.mock.method(globalThis, "fetch", async (url) => {
|
|
125
260
|
const u = String(url);
|
|
126
|
-
if (u.includes("
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (u.includes("duckduckgo")) return bin(600); // tier 4: real favicon
|
|
261
|
+
if (u.includes("glincker/thesvg")) return json(ICONS); // tier 1: no entry
|
|
262
|
+
// tier 2 (github) is never called: entity is unmapped
|
|
263
|
+
if (u.includes("duckduckgo")) return bin(600); // tier 3: real favicon
|
|
130
264
|
throw new Error(`unexpected fetch: ${u}`);
|
|
131
265
|
});
|
|
132
266
|
const res = await runProviders(getProviders("logo"), "search", "zzzbrand logo", {
|
|
@@ -136,19 +270,29 @@ test("the real logo cascade falls through tier by tier to the first hit", async
|
|
|
136
270
|
assert.equal(res.metadata.provider, "favicon.ddg");
|
|
137
271
|
});
|
|
138
272
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
273
|
+
test("githubAvatarSearch rejects private HEAD redirects", async (t) => {
|
|
274
|
+
const seen = [];
|
|
275
|
+
t.mock.method(globalThis, "fetch", async (url, options) => {
|
|
276
|
+
seen.push(url);
|
|
277
|
+
assert.equal(options.method, "HEAD");
|
|
278
|
+
assert.ok(options.signal);
|
|
279
|
+
return options.redirect === "manual"
|
|
280
|
+
? new Response(null, { status: 302, headers: { location: "http://127.0.0.1/private" } })
|
|
281
|
+
: new Response(null, { status: 200 });
|
|
282
|
+
});
|
|
283
|
+
assert.equal(await githubAvatarSearch("vercel logo"), null);
|
|
284
|
+
assert.equal(seen.length, 1);
|
|
285
|
+
assert.ok(!seen[0].includes("127.0.0.1"));
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("thesvgSearch rejects a manifest redirect to a private host", async (t) => {
|
|
289
|
+
resetThesvgManifest();
|
|
290
|
+
const seen = [];
|
|
291
|
+
t.mock.method(globalThis, "fetch", async (url, options) => {
|
|
292
|
+
seen.push(url);
|
|
293
|
+
assert.equal(options.redirect, "manual");
|
|
294
|
+
return new Response(null, { status: 302, headers: { location: "http://127.0.0.1/private" } });
|
|
153
295
|
});
|
|
154
|
-
|
|
296
|
+
assert.equal(await thesvgSearch("vercel logo"), null);
|
|
297
|
+
assert.equal(seen.length, 1);
|
|
298
|
+
});
|
|
@@ -69,3 +69,17 @@ export async function fetchMedia(url, { method = "GET", signal, fetchImpl = fetc
|
|
|
69
69
|
}
|
|
70
70
|
throw new Error("Media download exceeded redirect limit");
|
|
71
71
|
}
|
|
72
|
+
|
|
73
|
+
// Stream and abort once the cap is crossed, so a lying or chunked body can't buffer past it.
|
|
74
|
+
export async function readCappedBody(res, maxBytes, label) {
|
|
75
|
+
const declared = Number(res.headers.get("content-length"));
|
|
76
|
+
if (declared > maxBytes) throw new Error(`${label}: ${declared} bytes exceeds ${maxBytes} cap`);
|
|
77
|
+
const chunks = [];
|
|
78
|
+
let total = 0;
|
|
79
|
+
for await (const chunk of res.body) {
|
|
80
|
+
total += chunk.length;
|
|
81
|
+
if (total > maxBytes) throw new Error(`${label}: stream exceeds ${maxBytes} cap`);
|
|
82
|
+
chunks.push(chunk);
|
|
83
|
+
}
|
|
84
|
+
return Buffer.concat(chunks, total);
|
|
85
|
+
}
|
|
@@ -3,7 +3,7 @@ import { mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { test } from "node:test";
|
|
6
|
-
import { fetchMedia, isPublicMediaUrl } from "./media-fetch.mjs";
|
|
6
|
+
import { fetchMedia, isPublicMediaUrl, readCappedBody } from "./media-fetch.mjs";
|
|
7
7
|
import { freezeUrl } from "./freeze.mjs";
|
|
8
8
|
import { downloadTo } from "../../audio/scripts/lib/heygen.mjs";
|
|
9
9
|
import { synthesizeHeygen } from "../../audio/scripts/lib/tts.mjs";
|
|
@@ -167,3 +167,31 @@ for (const entry of ["heygen audio", "tts mp3", "tts wav", "favicon"]) {
|
|
|
167
167
|
}
|
|
168
168
|
});
|
|
169
169
|
}
|
|
170
|
+
|
|
171
|
+
function streamed(sizes, contentLength = null) {
|
|
172
|
+
let pulled = 0;
|
|
173
|
+
const body = (async function* () {
|
|
174
|
+
for (const n of sizes) {
|
|
175
|
+
pulled++;
|
|
176
|
+
yield new Uint8Array(n);
|
|
177
|
+
}
|
|
178
|
+
})();
|
|
179
|
+
return { res: { headers: { get: () => contentLength }, body }, pulled: () => pulled };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
test("readCappedBody refuses a declared oversize body before reading a byte", async () => {
|
|
183
|
+
const { res, pulled } = streamed([10], "101");
|
|
184
|
+
await assert.rejects(readCappedBody(res, 100, "t"), /t: 101 bytes exceeds 100 cap/);
|
|
185
|
+
assert.equal(pulled(), 0);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("readCappedBody aborts a chunked body the moment it crosses the cap", async () => {
|
|
189
|
+
const { res, pulled } = streamed([60, 60, 60]);
|
|
190
|
+
await assert.rejects(readCappedBody(res, 100, "t"), /t: stream exceeds 100 cap/);
|
|
191
|
+
assert.equal(pulled(), 2);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("readCappedBody returns every byte of a body at the cap", async () => {
|
|
195
|
+
const { res } = streamed([60, 40]);
|
|
196
|
+
assert.equal((await readCappedBody(res, 100, "t")).byteLength, 100);
|
|
197
|
+
});
|
|
@@ -25,12 +25,7 @@ import { sfxProvider } from "./sfx-provider.mjs";
|
|
|
25
25
|
import { bundledSfxProvider } from "./bundled-sfx-provider.mjs";
|
|
26
26
|
import { imageProvider, iconProvider } from "./image-provider.mjs";
|
|
27
27
|
import { brandProvider } from "./brand-provider.mjs";
|
|
28
|
-
import {
|
|
29
|
-
svglSearch,
|
|
30
|
-
simpleIconsSearch,
|
|
31
|
-
githubAvatarSearch,
|
|
32
|
-
faviconSearch,
|
|
33
|
-
} from "./logo-provider.mjs";
|
|
28
|
+
import { thesvgSearch, githubAvatarSearch, faviconSearch } from "./logo-provider.mjs";
|
|
34
29
|
import { heygenTtsGenerate } from "./voice-provider.mjs";
|
|
35
30
|
import { heygenVideoGenerate } from "./heygen-video-provider.mjs";
|
|
36
31
|
import { ltxVideoGenerate } from "./ltx-video-provider.mjs";
|
|
@@ -65,12 +60,11 @@ const REGISTRY = {
|
|
|
65
60
|
],
|
|
66
61
|
icon: [N("heygen.asset.search", { search: iconProvider.search })],
|
|
67
62
|
logo: [
|
|
68
|
-
// Official brand marks
|
|
69
|
-
//
|
|
70
|
-
// generic look-alike icons for brand queries. All free, all
|
|
71
|
-
// --local-only leaves only the cache rungs.
|
|
72
|
-
N("
|
|
73
|
-
N("simple-icons", { search: simpleIconsSearch }),
|
|
63
|
+
// Official brand marks: theSVG first, then GitHub org avatar and favicon
|
|
64
|
+
// for the brands it lacks. HeyGen asset search is deliberately absent — it
|
|
65
|
+
// returns generic look-alike icons for brand queries. All free, all
|
|
66
|
+
// network → --local-only leaves only the cache rungs.
|
|
67
|
+
N("thesvg", { search: thesvgSearch }),
|
|
74
68
|
N("github.avatar", { search: githubAvatarSearch }),
|
|
75
69
|
N("favicon.ddg", { search: faviconSearch }),
|
|
76
70
|
],
|
|
@@ -43,7 +43,7 @@ test("heygen provider is first for every type it serves except bundled sfx", ()
|
|
|
43
43
|
|
|
44
44
|
test("sanctioned providers only: heygen, local mflux/kokoro/ltx, codex, design spec, logo tiers", () => {
|
|
45
45
|
const allowed =
|
|
46
|
-
/^heygen|^bundled\.sfx$|^mflux\.local$|^kokoro\.local$|^ltx\.local$|^codex\.image_gen$|^design_spec$|^
|
|
46
|
+
/^heygen|^bundled\.sfx$|^mflux\.local$|^kokoro\.local$|^ltx\.local$|^codex\.image_gen$|^design_spec$|^thesvg$|^github\.avatar$|^favicon\.ddg$|^color_grade\.local$|^cube_lut\.local$/;
|
|
47
47
|
for (const t of listTypes()) {
|
|
48
48
|
for (const p of getProviders(t)) {
|
|
49
49
|
assert.ok(allowed.test(p.name), `${t} lists unsanctioned provider: ${p.name}`);
|