hyperframes 0.8.75 → 0.8.77
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 +2188 -1534
- package/dist/fontLocalizeCli.js +369 -151
- package/dist/hyperframe-runtime.js +62 -59
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +62 -59
- package/dist/hyperframes-player.global.js +3 -3
- package/dist/renderSetupWorker.js +113 -119
- package/dist/skills/hyperframes-cli/SKILL.md +2 -2
- package/dist/studio/assets/hyperframes-player-BSDTJ9EF.js +464 -0
- package/dist/studio/assets/{index-BlUYw1MB.js → index-2GgHjQq-.js} +163 -163
- package/dist/studio/assets/{index-0w6iIlwZ.css → index-BWJqxeaH.css} +1 -1
- package/dist/studio/assets/{index-BiFKcNjs.js → index-CsFCRdMo.js} +1 -1
- package/dist/studio/assets/{index-HjexPsny.js → index-DcxWQ7Za.js} +1 -1
- package/dist/studio/{chunk-7BP32G62.js → chunk-5VANA2TG.js} +12 -1
- package/dist/studio/chunk-5VANA2TG.js.map +1 -0
- package/dist/studio/{domEditingLayers-UKMVYXT6.js → domEditingLayers-IGPLTI5E.js} +4 -2
- package/dist/studio/index.d.ts +34 -69
- package/dist/studio/index.html +2 -2
- package/dist/studio/index.js +5287 -5277
- package/dist/studio/index.js.map +1 -1
- package/dist/studio/styles.css +1 -1
- package/package.json +1 -1
- package/dist/studio/assets/hyperframes-player-WbBVbWuO.js +0 -464
- package/dist/studio/chunk-7BP32G62.js.map +0 -1
- /package/dist/studio/{domEditingLayers-UKMVYXT6.js.map → domEditingLayers-IGPLTI5E.js.map} +0 -0
|
@@ -13009,6 +13009,13 @@ function parseGsapScriptAcornForWrite(script) {
|
|
|
13009
13009
|
}
|
|
13010
13010
|
}
|
|
13011
13011
|
function parseGsapScriptAcorn(script) {
|
|
13012
|
+
const parsed = parseMemo.get(script) ?? parseGsapScriptAcornUncached(script);
|
|
13013
|
+
parseMemo.delete(script);
|
|
13014
|
+
parseMemo.set(script, parsed);
|
|
13015
|
+
if (parseMemo.size > PARSE_MEMO_ENTRIES) parseMemo.delete(parseMemo.keys().next().value);
|
|
13016
|
+
return structuredClone(parsed);
|
|
13017
|
+
}
|
|
13018
|
+
function parseGsapScriptAcornUncached(script) {
|
|
13012
13019
|
try {
|
|
13013
13020
|
const ast = parseProgram2(script);
|
|
13014
13021
|
const scope = collectScopeBindings(ast);
|
|
@@ -13156,7 +13163,7 @@ function extractGsapLabels(script) {
|
|
|
13156
13163
|
return [];
|
|
13157
13164
|
}
|
|
13158
13165
|
}
|
|
13159
|
-
var PROPERTY_GROUPS, PROP_TO_GROUP, SKIP_KEYS, FUNCTION_TYPES, GSAP_METHODS, MAX_DEPTH, MAX_ITERS, SAFE_DEFAULT_NODES, SUPPORTED_PARAMS_CACHE, roundPercentage, GSAP_METHODS2, QUERY_METHODS, ITERATION_METHODS, SCOPE_NODE_TYPES, CONST_NODES, MATH_FNS, MATH_CONSTS, BUILTIN_VAR_KEYS, DROPPED_VAR_KEYS, EXTRAS_KEYS, PERCENTAGE_KEY_RE, GSAP_DEFAULT_DURATION;
|
|
13166
|
+
var PROPERTY_GROUPS, PROP_TO_GROUP, SKIP_KEYS, FUNCTION_TYPES, GSAP_METHODS, MAX_DEPTH, MAX_ITERS, SAFE_DEFAULT_NODES, SUPPORTED_PARAMS_CACHE, roundPercentage, GSAP_METHODS2, QUERY_METHODS, ITERATION_METHODS, SCOPE_NODE_TYPES, CONST_NODES, MATH_FNS, MATH_CONSTS, BUILTIN_VAR_KEYS, DROPPED_VAR_KEYS, EXTRAS_KEYS, PERCENTAGE_KEY_RE, GSAP_DEFAULT_DURATION, PARSE_MEMO_ENTRIES, parseMemo;
|
|
13160
13167
|
var init_gsapParserAcorn = __esm({
|
|
13161
13168
|
"../parsers/dist/gsapParserAcorn.js"() {
|
|
13162
13169
|
"use strict";
|
|
@@ -13236,9 +13243,14 @@ var init_gsapParserAcorn = __esm({
|
|
|
13236
13243
|
]);
|
|
13237
13244
|
PERCENTAGE_KEY_RE = /^(\d+(?:\.\d+)?)%$/;
|
|
13238
13245
|
GSAP_DEFAULT_DURATION = 0.5;
|
|
13246
|
+
PARSE_MEMO_ENTRIES = 4;
|
|
13247
|
+
parseMemo = /* @__PURE__ */ new Map();
|
|
13239
13248
|
}
|
|
13240
13249
|
});
|
|
13241
13250
|
|
|
13251
|
+
// src/renderSetupWorker.ts
|
|
13252
|
+
import { constants as constants2, setPriority } from "os";
|
|
13253
|
+
|
|
13242
13254
|
// src/browser/manager.ts
|
|
13243
13255
|
import { execSync, spawnSync } from "child_process";
|
|
13244
13256
|
import { randomUUID } from "crypto";
|
|
@@ -27624,6 +27636,81 @@ function collectDeclaredVariableIds(htmlTagRaw) {
|
|
|
27624
27636
|
}
|
|
27625
27637
|
return declared;
|
|
27626
27638
|
}
|
|
27639
|
+
function variablesDeclarationFindings(tag, tags) {
|
|
27640
|
+
const raw = readJsonAttr(tag.raw, "data-composition-variables");
|
|
27641
|
+
if (!raw) return [];
|
|
27642
|
+
let parsed;
|
|
27643
|
+
try {
|
|
27644
|
+
parsed = JSON.parse(raw);
|
|
27645
|
+
} catch (err) {
|
|
27646
|
+
const reason = err instanceof Error ? err.message : "unknown";
|
|
27647
|
+
return [
|
|
27648
|
+
{
|
|
27649
|
+
code: "invalid_composition_variables_declaration",
|
|
27650
|
+
severity: "error",
|
|
27651
|
+
message: `data-composition-variables is not valid JSON (${reason}).`,
|
|
27652
|
+
fixHint: `Provide a JSON array of variable declarations: data-composition-variables='[{"id":"title","type":"string","label":"Title","default":"Hello"}]'.`,
|
|
27653
|
+
snippet: truncateSnippet(tag.raw)
|
|
27654
|
+
}
|
|
27655
|
+
];
|
|
27656
|
+
}
|
|
27657
|
+
if (!Array.isArray(parsed)) {
|
|
27658
|
+
return [
|
|
27659
|
+
{
|
|
27660
|
+
code: "invalid_composition_variables_declaration",
|
|
27661
|
+
severity: "error",
|
|
27662
|
+
message: "data-composition-variables must be a JSON array of variable declarations.",
|
|
27663
|
+
fixHint: `Wrap declarations in [] and give each an id, type, label, and default: '[{"id":"title","type":"string","label":"Title","default":"Hello"}]'.`,
|
|
27664
|
+
snippet: truncateSnippet(tag.raw)
|
|
27665
|
+
}
|
|
27666
|
+
];
|
|
27667
|
+
}
|
|
27668
|
+
const findings = [];
|
|
27669
|
+
const knownTypes = new Set(COMPOSITION_VARIABLE_TYPES);
|
|
27670
|
+
const varSrcIds = /* @__PURE__ */ new Set();
|
|
27671
|
+
for (const other of tags) {
|
|
27672
|
+
const bound = readAttr(other.raw, "data-var-src");
|
|
27673
|
+
if (bound) varSrcIds.add(bound);
|
|
27674
|
+
}
|
|
27675
|
+
for (let i = 0; i < parsed.length; i += 1) {
|
|
27676
|
+
const entry = parsed[i];
|
|
27677
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
27678
|
+
findings.push({
|
|
27679
|
+
code: "invalid_composition_variables_declaration",
|
|
27680
|
+
severity: "error",
|
|
27681
|
+
message: `data-composition-variables entry [${i}] must be an object with id, type, label, and default.`,
|
|
27682
|
+
snippet: truncateSnippet(tag.raw)
|
|
27683
|
+
});
|
|
27684
|
+
continue;
|
|
27685
|
+
}
|
|
27686
|
+
const e = entry;
|
|
27687
|
+
const missing = [];
|
|
27688
|
+
if (typeof e.id !== "string") missing.push("id");
|
|
27689
|
+
if (typeof e.type !== "string" || !knownTypes.has(e.type)) missing.push("type");
|
|
27690
|
+
if (typeof e.label !== "string") missing.push("label");
|
|
27691
|
+
if (!("default" in e)) missing.push("default");
|
|
27692
|
+
if (missing.length > 0) {
|
|
27693
|
+
findings.push({
|
|
27694
|
+
code: "invalid_composition_variables_declaration",
|
|
27695
|
+
severity: "error",
|
|
27696
|
+
message: `data-composition-variables entry [${i}] is missing or has invalid: ${missing.join(", ")}. Type must be one of string, number, color, boolean, enum, font, image.`,
|
|
27697
|
+
snippet: truncateSnippet(tag.raw)
|
|
27698
|
+
});
|
|
27699
|
+
continue;
|
|
27700
|
+
}
|
|
27701
|
+
const id = String(e.id);
|
|
27702
|
+
if ((e.type === "image" || varSrcIds.has(id)) && typeof e.default === "string" && e.default.length > 0 && !isSafeMediaUrl(e.default)) {
|
|
27703
|
+
findings.push({
|
|
27704
|
+
code: "unloadable_media_variable_default",
|
|
27705
|
+
severity: "error",
|
|
27706
|
+
message: `Variable "${id}" defaults to a URL the runtime will refuse to load, so any element bound to it renders its authored fallback src instead and the render still exits 0.`,
|
|
27707
|
+
fixHint: `Media URLs must be relative, http(s), blob:, or a data:image/* URI. Copy the file into the project and reference it relatively (e.g. "assets/bg.png") rather than by absolute path.`,
|
|
27708
|
+
snippet: truncateSnippet(tag.raw)
|
|
27709
|
+
});
|
|
27710
|
+
}
|
|
27711
|
+
}
|
|
27712
|
+
return findings;
|
|
27713
|
+
}
|
|
27627
27714
|
function collectAllDeclaredVariableIds(tags) {
|
|
27628
27715
|
const all = /* @__PURE__ */ new Set();
|
|
27629
27716
|
for (const tag of tags) {
|
|
@@ -28099,84 +28186,8 @@ var compositionRules = [
|
|
|
28099
28186
|
// on any structural problem. Surface JSON / shape failures so authors
|
|
28100
28187
|
// catch them at lint time rather than wondering why their `getVariables()`
|
|
28101
28188
|
// defaults aren't applied.
|
|
28102
|
-
//
|
|
28103
|
-
({ tags }) =>
|
|
28104
|
-
const htmlTag = findHtmlTag(tags);
|
|
28105
|
-
if (!htmlTag) return [];
|
|
28106
|
-
const raw = readJsonAttr(htmlTag.raw, "data-composition-variables");
|
|
28107
|
-
if (!raw) return [];
|
|
28108
|
-
let parsed;
|
|
28109
|
-
try {
|
|
28110
|
-
parsed = JSON.parse(raw);
|
|
28111
|
-
} catch (err) {
|
|
28112
|
-
const reason = err instanceof Error ? err.message : "unknown";
|
|
28113
|
-
return [
|
|
28114
|
-
{
|
|
28115
|
-
code: "invalid_composition_variables_declaration",
|
|
28116
|
-
severity: "error",
|
|
28117
|
-
message: `data-composition-variables is not valid JSON (${reason}).`,
|
|
28118
|
-
fixHint: `Provide a JSON array of variable declarations: data-composition-variables='[{"id":"title","type":"string","label":"Title","default":"Hello"}]'.`,
|
|
28119
|
-
snippet: truncateSnippet(htmlTag.raw)
|
|
28120
|
-
}
|
|
28121
|
-
];
|
|
28122
|
-
}
|
|
28123
|
-
if (!Array.isArray(parsed)) {
|
|
28124
|
-
return [
|
|
28125
|
-
{
|
|
28126
|
-
code: "invalid_composition_variables_declaration",
|
|
28127
|
-
severity: "error",
|
|
28128
|
-
message: "data-composition-variables must be a JSON array of variable declarations.",
|
|
28129
|
-
fixHint: `Wrap declarations in [] and give each an id, type, label, and default: '[{"id":"title","type":"string","label":"Title","default":"Hello"}]'.`,
|
|
28130
|
-
snippet: truncateSnippet(htmlTag.raw)
|
|
28131
|
-
}
|
|
28132
|
-
];
|
|
28133
|
-
}
|
|
28134
|
-
const findings = [];
|
|
28135
|
-
const knownTypes = new Set(COMPOSITION_VARIABLE_TYPES);
|
|
28136
|
-
const varSrcIds = /* @__PURE__ */ new Set();
|
|
28137
|
-
for (const tag of tags) {
|
|
28138
|
-
const bound = readAttr(tag.raw, "data-var-src");
|
|
28139
|
-
if (bound) varSrcIds.add(bound);
|
|
28140
|
-
}
|
|
28141
|
-
for (let i = 0; i < parsed.length; i += 1) {
|
|
28142
|
-
const entry = parsed[i];
|
|
28143
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
28144
|
-
findings.push({
|
|
28145
|
-
code: "invalid_composition_variables_declaration",
|
|
28146
|
-
severity: "error",
|
|
28147
|
-
message: `data-composition-variables entry [${i}] must be an object with id, type, label, and default.`,
|
|
28148
|
-
snippet: truncateSnippet(htmlTag.raw)
|
|
28149
|
-
});
|
|
28150
|
-
continue;
|
|
28151
|
-
}
|
|
28152
|
-
const e = entry;
|
|
28153
|
-
const missing = [];
|
|
28154
|
-
if (typeof e.id !== "string") missing.push("id");
|
|
28155
|
-
if (typeof e.type !== "string" || !knownTypes.has(e.type)) missing.push("type");
|
|
28156
|
-
if (typeof e.label !== "string") missing.push("label");
|
|
28157
|
-
if (!("default" in e)) missing.push("default");
|
|
28158
|
-
if (missing.length > 0) {
|
|
28159
|
-
findings.push({
|
|
28160
|
-
code: "invalid_composition_variables_declaration",
|
|
28161
|
-
severity: "error",
|
|
28162
|
-
message: `data-composition-variables entry [${i}] is missing or has invalid: ${missing.join(", ")}. Type must be one of string, number, color, boolean, enum, font, image.`,
|
|
28163
|
-
snippet: truncateSnippet(htmlTag.raw)
|
|
28164
|
-
});
|
|
28165
|
-
continue;
|
|
28166
|
-
}
|
|
28167
|
-
const id = String(e.id);
|
|
28168
|
-
if ((e.type === "image" || varSrcIds.has(id)) && typeof e.default === "string" && e.default.length > 0 && !isSafeMediaUrl(e.default)) {
|
|
28169
|
-
findings.push({
|
|
28170
|
-
code: "unloadable_media_variable_default",
|
|
28171
|
-
severity: "error",
|
|
28172
|
-
message: `Variable "${id}" defaults to a URL the runtime will refuse to load, so any element bound to it renders its authored fallback src instead and the render still exits 0.`,
|
|
28173
|
-
fixHint: `Media URLs must be relative, http(s), blob:, or a data:image/* URI. Copy the file into the project and reference it relatively (e.g. "assets/bg.png") rather than by absolute path.`,
|
|
28174
|
-
snippet: truncateSnippet(htmlTag.raw)
|
|
28175
|
-
});
|
|
28176
|
-
}
|
|
28177
|
-
}
|
|
28178
|
-
return findings;
|
|
28179
|
-
},
|
|
28189
|
+
// Checked on every element that declares: <html>, or the composition root.
|
|
28190
|
+
({ tags }) => tags.filter((tag) => readJsonAttr(tag.raw, "data-composition-variables")).flatMap((tag) => variablesDeclarationFindings(tag, tags)),
|
|
28180
28191
|
// html_dir_attribute_breaks_render — valid non-LTR dir values on
|
|
28181
28192
|
// <html> renders correctly in preview/snapshot but produces a fully
|
|
28182
28193
|
// blank/black video from render, with no other lint/validate/inspect
|
|
@@ -31837,63 +31848,41 @@ function isSnippetFragment(html) {
|
|
|
31837
31848
|
}
|
|
31838
31849
|
|
|
31839
31850
|
// src/utils/orphanCleanup.ts
|
|
31840
|
-
import { execFileSync as execFileSync3
|
|
31851
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
31841
31852
|
import { readFileSync as readFileSync4 } from "fs";
|
|
31842
31853
|
function killOrphanedProcesses() {
|
|
31843
31854
|
if (process.platform === "win32") return 0;
|
|
31844
|
-
|
|
31845
|
-
|
|
31846
|
-
|
|
31847
|
-
|
|
31848
|
-
|
|
31849
|
-
return killed;
|
|
31855
|
+
return killOrphansMatching([
|
|
31856
|
+
"chrome-headless-shell",
|
|
31857
|
+
"chrome_headless_shell",
|
|
31858
|
+
"puppeteer_dev_chrome_profile"
|
|
31859
|
+
]);
|
|
31850
31860
|
}
|
|
31851
31861
|
function killProcessTree(pid, signal = "SIGTERM") {
|
|
31852
31862
|
void terminateProcessTree(pid, { signal }).catch(() => void 0);
|
|
31853
31863
|
}
|
|
31854
|
-
function
|
|
31855
|
-
const uid =
|
|
31856
|
-
const
|
|
31857
|
-
let
|
|
31864
|
+
function killOrphansMatching(markers) {
|
|
31865
|
+
const uid = process.getuid?.();
|
|
31866
|
+
const owner = uid === void 0 ? ["-A"] : ["-U", String(uid)];
|
|
31867
|
+
let table;
|
|
31858
31868
|
try {
|
|
31859
|
-
|
|
31869
|
+
table = execFileSync3("ps", [...owner, "-ww", "-o", "pid=,ppid=,args="], {
|
|
31860
31870
|
encoding: "utf-8",
|
|
31861
31871
|
timeout: 3e3
|
|
31862
|
-
})
|
|
31863
|
-
if (!raw) return 0;
|
|
31864
|
-
pids = raw.split("\n").map((s) => parseInt(s, 10)).filter((n) => !isNaN(n) && n > 0);
|
|
31872
|
+
});
|
|
31865
31873
|
} catch {
|
|
31866
31874
|
return 0;
|
|
31867
31875
|
}
|
|
31868
31876
|
let killed = 0;
|
|
31869
|
-
for (const
|
|
31870
|
-
|
|
31871
|
-
|
|
31877
|
+
for (const line of table.split("\n")) {
|
|
31878
|
+
const match = line.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);
|
|
31879
|
+
if (!match || match[2] !== "1") continue;
|
|
31880
|
+
if (!markers.some((marker) => match[3].includes(marker))) continue;
|
|
31881
|
+
killProcessTree(Number(match[1]));
|
|
31872
31882
|
killed++;
|
|
31873
31883
|
}
|
|
31874
31884
|
return killed;
|
|
31875
31885
|
}
|
|
31876
|
-
var _cachedUid;
|
|
31877
|
-
function getUid() {
|
|
31878
|
-
if (_cachedUid !== void 0) return _cachedUid;
|
|
31879
|
-
try {
|
|
31880
|
-
_cachedUid = execSync2("id -u", { encoding: "utf-8", timeout: 1e3 }).trim();
|
|
31881
|
-
} catch {
|
|
31882
|
-
_cachedUid = null;
|
|
31883
|
-
}
|
|
31884
|
-
return _cachedUid;
|
|
31885
|
-
}
|
|
31886
|
-
function isOrphan(pid) {
|
|
31887
|
-
try {
|
|
31888
|
-
const ppid = execSync2(`ps -p ${pid} -o ppid=`, {
|
|
31889
|
-
encoding: "utf-8",
|
|
31890
|
-
timeout: 2e3
|
|
31891
|
-
}).trim();
|
|
31892
|
-
return ppid === "1";
|
|
31893
|
-
} catch {
|
|
31894
|
-
return false;
|
|
31895
|
-
}
|
|
31896
|
-
}
|
|
31897
31886
|
|
|
31898
31887
|
// src/renderSetupWorkerLifecycle.ts
|
|
31899
31888
|
var RENDER_SETUP_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
@@ -31928,7 +31917,12 @@ var result;
|
|
|
31928
31917
|
if (mode === "browser") {
|
|
31929
31918
|
result = await ensureBrowser(input);
|
|
31930
31919
|
} else if (mode === "lint") {
|
|
31931
|
-
|
|
31920
|
+
setPriority(constants2.priority.PRIORITY_LOW);
|
|
31921
|
+
result = await lintProject(
|
|
31922
|
+
input.projectDir,
|
|
31923
|
+
input.entryFile,
|
|
31924
|
+
input.host ? { host: input.host } : void 0
|
|
31925
|
+
);
|
|
31932
31926
|
} else if (mode === "orphan-cleanup") {
|
|
31933
31927
|
const killed = killOrphanedProcesses();
|
|
31934
31928
|
result = killed;
|
|
@@ -69,12 +69,12 @@ Treat tiny unstyled content, canvas-sized icons, missing hero elements, or timel
|
|
|
69
69
|
|
|
70
70
|
## Agent conventions
|
|
71
71
|
|
|
72
|
-
- **Search the catalog before writing motion by hand.** `npx hyperframes catalog --query "<the beat, in plain English>"`. Search is entirely local: there is no hosted tier, no account, and the query text is never sent anywhere. By default it ranks on vocabulary shared with the item's name, title and
|
|
72
|
+
- **Search the catalog before writing motion by hand.** `npx hyperframes catalog --query "<the beat, in plain English>"`. Search is entirely local: there is no hosted tier, no account, and the query text is never sent anywhere. By default it ranks on vocabulary shared with the item's name, title, description and tags, which misses any phrasing that does not reuse the catalog's own wording. Add `--on-device` to rank by meaning instead (see the offline tier below).
|
|
73
73
|
- **Query in English even when the video is not.** Both tiers index an English catalog, so a query in another script produces no searchable terms and returns nothing. Describe the move in English; the on-screen copy stays in whatever language the video needs. `No searchable words in query` means exactly this and is not a missing component, so do not report it as a catalog gap.
|
|
74
74
|
- **Read which tier answered; never infer it from results appearing.** With `--json` the envelope carries `query`, `tier` (`on-device` or `words`), `tier_detail`, `dropped`, `unindexed`, `shown`, `total` and `results`, plus `top_score` when the answering tier produces one and `warnings` when a tier was asked for and could not run, or when a search returned nothing and a better tier is still waiting on someone's consent. A weak result on `words` is expected; the same result on `on-device` is a bug. `top_score` is on-device only and has no threshold behind it: the ranker returns the whole catalog in some order for every query, so read it as evidence rather than as a pass or fail.
|
|
75
75
|
- **`dropped` and `unindexed` are opposite skews between the registry and the on-device index, and rewording the query fixes neither.** `dropped` counts ranked names this registry cannot install, so the strongest matches are the ones being lost. `unindexed` counts registry moves the index cannot see at all, which no query can ever return. Refreshing the registry is not the answer to either: its manifest carries a 24h TTL and heals itself, while the vectors are a separately published artifact fetched into `~/.hyperframes/catalog/`. Re-running with `--on-device` refetches that index when `unindexed` is above zero, so that is the remedy to hand the user. A pure over-coverage skew (`dropped` above zero while `unindexed` is zero) does not trigger the refetch; clearing `~/.hyperframes/catalog/` is the only way out of that one. Both counts are of names rather than of results, so either can exceed `total`.
|
|
76
76
|
- **When a search comes back with nothing worth installing, say so.** `npx hyperframes feedback --search-miss "<the query you ran>" --wanted "<the move you needed>" --tier <the tier that answered>`. You do not have to assemble that line: `catalog --query` prints it pre-filled, and every `--json` search envelope carries it as `report_gap` with the query and tier already correct — fill in `--wanted` and send. This is the only path that sends a query anywhere, and it is a separate deliberate command precisely so plain `catalog --query` keeps its promise of sending nothing. **Report on either tier**, whenever the results do not do the thing; do not hold out for the on-device tier, which needs a consented 33 MB download and is therefore off in most agent runs — waiting for it means never reporting at all. The tier rides along in the report, so a vocabulary miss stays distinguishable from a meaning miss without you having to judge which one you hit. What comes back is a list of moves the catalog does not have yet, read directly rather than guessed from install counts, so the phrasing that matters is the effect you wanted, not the item name you imagined. It carries no rating and never lands in the rating metric.
|
|
77
|
-
- **Offer the offline tier; never enable it silently.** A one-time ~33 MB download (a quantized ONNX build of `bge-small-en-v1.5` plus its tokenizer, pinned to a fixed revision) and the catalog vectors from the registry, both cached under `~/.hyperframes/`, neither added to the project or any package. Once cached it ranks by meaning with nothing sent. Say the size out loud and let the person decide, then pass `--on-device` (with `-y` to skip the prompt) once they agree. The interactive offer only fires on a TTY. Under `--json` there is no prompt, but a search that found nothing puts the same ask in `warnings`, so read that array and put the decision to the user yourself.
|
|
77
|
+
- **Offer the offline tier; never enable it silently.** A one-time ~33 MB download (a quantized ONNX build of `bge-small-en-v1.5` plus its tokenizer, pinned to a fixed revision) and the catalog vectors from the registry, both cached under `~/.hyperframes/`, neither added to the project or any package. Once cached it ranks by meaning with nothing sent. Say the size out loud and let the person decide, then pass `--on-device` (with `-y` to skip the prompt) once they agree. The interactive offer only fires on a TTY. Under `--json` there is no prompt, but a search that found nothing puts the same ask in `warnings`, so read that array and put the decision to the user yourself. When the person asks what the download is, why this model, or what leaves the machine, point them to https://hyperframes.heygen.com/developers/catalog-search.
|
|
78
78
|
|
|
79
79
|
- Prefer `--json` for agent and CI calls. Server-mode `render`, `preview`, and `play` do not provide ordinary JSON output; `preview --selection --json` and `preview --context --json` are query-mode exceptions.
|
|
80
80
|
- `doctor --json` always exits zero. Gate on its payload:
|