@hypit/hypit 0.1.12 → 0.1.14
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/README.md +36 -2
- package/bin/hypit.mjs +0 -2
- package/dist/public/model-kit.d.ts +16 -0
- package/package.json +3 -1
- package/packages/cli/README.md +3 -0
- package/packages/cli/package.json +1 -0
- package/packages/cli/src/commands/environment.ts +12 -7
- package/packages/cli/src/commands/results.ts +19 -3
- package/packages/cli/src/main.ts +2 -1
- package/packages/cli/src/oauth.ts +50 -7
- package/packages/cli/src/output.ts +2 -2
- package/packages/cli/src/source-discovery.ts +2 -2
- package/packages/credential-store-file/README.md +60 -0
- package/packages/credential-store-file/package.json +21 -0
- package/packages/credential-store-file/src/activation.ts +29 -0
- package/packages/credential-store-file/src/index.ts +1 -0
- package/packages/credential-store-file/src/store.ts +89 -0
- package/packages/fonts-open/src/surface.ts +8 -2
- package/packages/hyperframes/README.md +16 -2
- package/packages/hyperframes/src/browser-program.ts +6 -1
- package/packages/hyperframes/src/document.ts +31 -21
- package/packages/hyperframes/src/project.ts +11 -20
- package/packages/media-execution/README.md +7 -0
- package/packages/media-execution/src/execute.ts +5 -5
- package/packages/media-execution/src/index.ts +1 -1
- package/packages/media-execution/src/process-env.ts +20 -0
- package/packages/media-execution/src/surface.ts +83 -76
- package/packages/model-kit/README.md +28 -0
- package/packages/model-kit/src/index.ts +58 -19
- package/packages/package-loader-node/README.md +10 -0
- package/packages/package-loader-node/src/index.ts +1 -0
- package/packages/package-loader-node/src/loader.ts +25 -5
- package/packages/package-loader-node/src/location.ts +11 -2
- package/packages/project-context-node/README.md +2 -0
- package/packages/project-context-node/src/project-context.ts +5 -3
- package/packages/provider-hyperframes-local/README.md +95 -13
- package/packages/provider-hyperframes-local/package.json +13 -3
- package/packages/provider-hyperframes-local/src/activation.ts +20 -6
- package/packages/provider-hyperframes-local/src/browser-install.ts +5 -0
- package/packages/provider-hyperframes-local/src/browser.ts +118 -0
- package/packages/provider-hyperframes-local/src/capture-bootstrap.ts +4 -6
- package/packages/provider-hyperframes-local/src/capture-exit.ts +24 -0
- package/packages/provider-hyperframes-local/src/capture-process.ts +54 -59
- package/packages/provider-hyperframes-local/src/capture-worker.ts +28 -7
- package/packages/provider-hyperframes-local/src/capture.ts +12 -5
- package/packages/provider-hyperframes-local/src/opaque-capture.ts +46 -18
- package/packages/provider-hyperframes-local/src/options.ts +2 -2
- package/packages/provider-hyperframes-local/src/process-tree.ts +82 -0
- package/packages/provider-hyperframes-local/src/process.ts +22 -0
- package/packages/provider-hyperframes-local/src/program.ts +25 -33
- package/packages/provider-hyperframes-local/src/provider.ts +4 -7
- package/packages/provider-hyperframes-local/src/render.ts +9 -4
- package/packages/provider-hypihub/README.md +3 -0
- package/packages/runtime-host-node/README.md +5 -0
- package/packages/runtime-host-node/src/index.ts +7 -2
- package/packages/runtime-host-node/src/packages.ts +13 -7
- package/packages/runtime-local/README.md +10 -0
- package/packages/runtime-local/src/config.ts +1 -1
- package/packages/runtime-local/src/credentials.ts +10 -5
- package/packages/runtime-local/src/runtime.ts +1 -1
- package/packages/seedance/README.md +42 -0
- package/packages/seedance/src/index.ts +5 -22
- package/packages/seedance/src/surface.ts +2 -3
- package/packages/seedance/src/validation.ts +16 -0
- package/packages/studio/src/preview/runtime-shim.ts +17 -3
- package/packages/studio/src/server.ts +2 -2
- package/packages/video-cli/README.md +5 -1
- package/packages/video-cli/package.json +1 -0
- package/packages/video-cli/src/distribution.ts +3 -10
- package/packages/video-cli/src/version.ts +1 -1
- package/packages/workspace-fs-node/src/workspace.ts +2 -2
- package/packages/yt-dlp/README.md +3 -2
- package/packages/yt-dlp/package.json +4 -0
- package/packages/yt-dlp/src/download.ts +10 -14
package/README.md
CHANGED
|
@@ -140,13 +140,47 @@ commands and the repository layout.
|
|
|
140
140
|
</tr>
|
|
141
141
|
<tr>
|
|
142
142
|
<td>Questions</td>
|
|
143
|
-
<td><a href="https://discord.gg/85hnyQnxpn">Discord</a> or <a href="https://t.me/
|
|
143
|
+
<td><a href="https://discord.gg/85hnyQnxpn">Discord</a> or <a href="https://t.me/hypitai">Telegram</a></td>
|
|
144
|
+
</tr>
|
|
145
|
+
</table>
|
|
146
|
+
|
|
147
|
+
## Launch Partner
|
|
148
|
+
|
|
149
|
+
<table>
|
|
150
|
+
<tr>
|
|
151
|
+
<td width="220" align="center">
|
|
152
|
+
<a href="https://openagents.org/blog/posts/2026-09-08-pilot-user-program-zh-hypit"><img alt="OpenAgents" src="./docs/public/openagents.png" width="180"></a>
|
|
153
|
+
</td>
|
|
154
|
+
<td>OpenAgents Workspace is a product that helps you manage AI agents, allowing you to invoke agents running on any device (whether on a mobile phone or computer) anytime, anywhere. Agents can collaborate within the platform to help you complete tasks and boost efficiency. It currently supports integration with over 20 types of agents, including Claude, Codex, OpenClaw, and Hermes (Details on supported models: <a href="https://api-gateway.openagents.org/static/index.html">https://api-gateway.openagents.org/static/index.html</a>)</td>
|
|
155
|
+
</tr>
|
|
156
|
+
<tr>
|
|
157
|
+
<td width="220" align="center">
|
|
158
|
+
<a href="https://monid.ai?fpr=alvin-45480b">
|
|
159
|
+
<picture>
|
|
160
|
+
<source media="(prefers-color-scheme: dark)" srcset="./docs/public/monid-logo-white.svg">
|
|
161
|
+
<img alt="Monid" src="./docs/public/monid-logo-blue.svg" width="150">
|
|
162
|
+
</picture>
|
|
163
|
+
</a>
|
|
164
|
+
</td>
|
|
165
|
+
<td>Monid is the OpenRouter for agent tools: one key, one wallet, hundreds of tools, billed per call. Hypit can route Seedance video, images, speech and word-level transcription to Monid.</td>
|
|
166
|
+
</tr>
|
|
167
|
+
<tr>
|
|
168
|
+
<td width="220" align="center">
|
|
169
|
+
<a href="https://www.hiapi.ai"><img alt="HiAPI" src="./docs/public/hiapi.png" width="165"></a>
|
|
170
|
+
</td>
|
|
171
|
+
<td>HiAPI is a unified API platform for AI image, video, audio, and language models. Connect GPT Image 2.5, Seedance 2.0, Veo 3.1, Kling, Wan, and other popular models through one platform. Pay as you go with no monthly fee, with free trial credits available for new users.</td>
|
|
144
172
|
</tr>
|
|
145
173
|
</table>
|
|
146
174
|
|
|
147
175
|
## ⭐ Star History
|
|
148
176
|
|
|
149
|
-
|
|
177
|
+
<a href="https://www.star-history.com/?repos=hypit-ai%2Fhypit&type=date&legend=top-left">
|
|
178
|
+
<picture>
|
|
179
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=hypit-ai/hypit&type=date&theme=dark&logscale&legend=top-left" />
|
|
180
|
+
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=hypit-ai/hypit&type=date&logscale&legend=top-left" />
|
|
181
|
+
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=hypit-ai/hypit&type=date&logscale&legend=top-left" />
|
|
182
|
+
</picture>
|
|
183
|
+
</a>
|
|
150
184
|
|
|
151
185
|
## Contributors
|
|
152
186
|
|
package/bin/hypit.mjs
CHANGED
|
@@ -19,8 +19,6 @@ process.emitWarning = function hypitWarning(warning, ...args) {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
const distributionRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
22
|
-
process.env.HYPIT_CLI_LAUNCHER ??= import.meta.filename;
|
|
23
|
-
process.env.HYPIT_DISTRIBUTION_ROOT ??= distributionRoot;
|
|
24
22
|
register();
|
|
25
23
|
const {
|
|
26
24
|
installDistributionPackageResolution,
|
|
@@ -380,6 +380,15 @@ type GenerationMediaValue = {
|
|
|
380
380
|
};
|
|
381
381
|
type GenerationPortValue = string | number | boolean | GenerationMediaValue;
|
|
382
382
|
|
|
383
|
+
/**
|
|
384
|
+
* The one request envelope every exact model uses. A Provider reads ports by
|
|
385
|
+
* name and never learns a model-specific field layout, so the same wire mapping
|
|
386
|
+
* mechanism serves every model and every service reselling it.
|
|
387
|
+
*/
|
|
388
|
+
type GenerationRequest = {
|
|
389
|
+
/** An absent port is an omitted key. A present port always carries at least one value. */
|
|
390
|
+
readonly ports: Readonly<Record<string, readonly GenerationPortValue[]>>;
|
|
391
|
+
};
|
|
383
392
|
/**
|
|
384
393
|
* A request while graph inputs are still being attached. It is deliberately a
|
|
385
394
|
* Providers can only receive the finalized request after the exact model
|
|
@@ -423,6 +432,10 @@ type ExactModelEndpointSpec = {
|
|
|
423
432
|
readonly requestTypeName: string;
|
|
424
433
|
readonly producerName: string;
|
|
425
434
|
readonly ports: GenerationPortTable;
|
|
435
|
+
/** Additional complete-request rules, after port and supplied-input validation. */
|
|
436
|
+
readonly validateRequest?: (request: GenerationRequest) => void;
|
|
437
|
+
/** Pure, synchronous checks of supplied values, in drafts and complete requests; throw to reject. */
|
|
438
|
+
readonly validateInputs?: (inputs: GenerationRequestDraft) => void;
|
|
426
439
|
};
|
|
427
440
|
type ExactModelEndpoint = {
|
|
428
441
|
readonly key: string;
|
|
@@ -435,6 +448,9 @@ type ExactModelEndpoint = {
|
|
|
435
448
|
readonly mediaBindings: Readonly<Record<string, ExactModelMediaBindingEndpoint>>;
|
|
436
449
|
readonly textBindings: Readonly<Record<string, ExactModelTextBindingEndpoint>>;
|
|
437
450
|
readonly ports: GenerationPortTable;
|
|
451
|
+
readonly validateRequest: (request: unknown) => void;
|
|
452
|
+
readonly validateDraft: (draft: unknown) => void;
|
|
453
|
+
readonly sealRequest: (ports: GenerationRequest["ports"]) => GenerationRequest;
|
|
438
454
|
readonly fragment: ReturnType<typeof sealGraphFragment>;
|
|
439
455
|
};
|
|
440
456
|
type ExactModelMediaBindingEndpoint = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypit/hypit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"homepage": "https://hypit.ai",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -168,6 +168,7 @@
|
|
|
168
168
|
],
|
|
169
169
|
"scripts": {
|
|
170
170
|
"pack:distribution": "node scripts/pack-distribution.mjs",
|
|
171
|
+
"check:distribution": "node scripts/check-distribution.mjs",
|
|
171
172
|
"build:public-types": "node scripts/build-public-types.mjs",
|
|
172
173
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
173
174
|
"docs:dev": "vitepress dev docs",
|
|
@@ -202,6 +203,7 @@
|
|
|
202
203
|
"@hypit/comment-sticker-studio": "workspace:*",
|
|
203
204
|
"@hypit/composition": "workspace:*",
|
|
204
205
|
"@hypit/credential-store-env": "workspace:*",
|
|
206
|
+
"@hypit/credential-store-file": "workspace:*",
|
|
205
207
|
"@hypit/credential-store-os": "workspace:*",
|
|
206
208
|
"@hypit/deck-track": "workspace:*",
|
|
207
209
|
"@hypit/deck-track-studio": "workspace:*",
|
package/packages/cli/README.md
CHANGED
|
@@ -148,6 +148,9 @@ from a lookup that still needs the Build's Runtime or correct project selection.
|
|
|
148
148
|
Worker process log instead, for Runtime startup or process-level failures.
|
|
149
149
|
|
|
150
150
|
Project context resolution is owned by [`@hypit/project-context-node`](../project-context-node/README.md).
|
|
151
|
+
History Source filters resolve existing filesystem links before comparing project-relative Result
|
|
152
|
+
paths. A deleted Source or directory remains queryable: only its existing ancestor is resolved and
|
|
153
|
+
the missing path suffix is retained. This is local argument handling, with no saved alias inventory.
|
|
151
154
|
CLI, Studio and creation tools call that same package; the CLI is not another environment owner.
|
|
152
155
|
|
|
153
156
|
`doctor`, `programs up|status|down`, and `runtime up` accept repeated `--endpoint <instance>` values.
|
|
@@ -364,13 +364,10 @@ export async function runEnvironmentCommand(input: {
|
|
|
364
364
|
}
|
|
365
365
|
const credentialsControl = await (await runtimeHost(runtimeProfile)).openCredentials(args.endpoint);
|
|
366
366
|
try {
|
|
367
|
-
let credentials = await credentialsControl.credentials(args.endpoint);
|
|
368
|
-
if (args.slot !== undefined) credentials = credentials.filter((item) => item.slot === args.slot);
|
|
369
|
-
if (credentials.length === 0) throw new Error(`Endpoint ${args.endpoint} has no matching credential`);
|
|
370
|
-
if (args.slot === undefined && credentials.length > 1 && args.action !== "status") {
|
|
371
|
-
throw new Error(`Endpoint ${args.endpoint} has several credentials; select one with --slot`);
|
|
372
|
-
}
|
|
373
367
|
if (args.action === "status") {
|
|
368
|
+
let credentials = await credentialsControl.credentials(args.endpoint);
|
|
369
|
+
if (args.slot !== undefined) credentials = credentials.filter((item) => item.slot === args.slot);
|
|
370
|
+
if (credentials.length === 0) throw new Error(`Endpoint ${args.endpoint} has no matching credential`);
|
|
374
371
|
const view = credentials.slice(0, args.limit).map((item) => ({
|
|
375
372
|
endpoint: item.endpoint,
|
|
376
373
|
slot: item.slot,
|
|
@@ -397,7 +394,15 @@ export async function runEnvironmentCommand(input: {
|
|
|
397
394
|
: `login opens OAuth: ${item.acquisition.authorizationEndpoint}`;
|
|
398
395
|
return `${item.slot}: ${item.configured ? "configured" : "missing"} · ${item.writable ? "writable" : "read-only"} · ${entry}`;
|
|
399
396
|
}));
|
|
400
|
-
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
let credentials = await credentialsControl.describeCredentials(args.endpoint);
|
|
400
|
+
if (args.slot !== undefined) credentials = credentials.filter((item) => item.slot === args.slot);
|
|
401
|
+
if (credentials.length === 0) throw new Error(`Endpoint ${args.endpoint} has no matching credential`);
|
|
402
|
+
if (args.slot === undefined && credentials.length > 1) {
|
|
403
|
+
throw new Error(`Endpoint ${args.endpoint} has several credentials; select one with --slot`);
|
|
404
|
+
}
|
|
405
|
+
if (args.action === "login") {
|
|
401
406
|
const [item] = credentials;
|
|
402
407
|
if (item === undefined) throw new Error(`Endpoint ${args.endpoint} has no matching credential`);
|
|
403
408
|
if (!item.writable) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { realpath } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, resolve } from "node:path";
|
|
2
3
|
|
|
3
4
|
import type { BuildResultRepository } from "@hypit/build-result";
|
|
4
5
|
|
|
@@ -20,6 +21,21 @@ export function isProjectResultCommand(args: CliCommand): args is ProjectResultC
|
|
|
20
21
|
|| args.command === "get" || (args.command === "result" && args.action === "edit");
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
/** History can name a deleted Source. Resolve existing directory links, keeping the absent suffix. */
|
|
25
|
+
async function historySourcePath(path: string): Promise<string> {
|
|
26
|
+
let existing = resolve(path);
|
|
27
|
+
const suffix: string[] = [];
|
|
28
|
+
while (true) {
|
|
29
|
+
try { return resolve(await realpath(existing), ...suffix); }
|
|
30
|
+
catch (error) {
|
|
31
|
+
const parent = dirname(existing);
|
|
32
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT" || parent === existing) throw error;
|
|
33
|
+
suffix.unshift(basename(existing));
|
|
34
|
+
existing = parent;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
23
39
|
/** Execute commands that need only project-owned Result history, never a Runtime. */
|
|
24
40
|
export async function runProjectResultCommand(input: {
|
|
25
41
|
readonly args: ProjectResultCommand;
|
|
@@ -62,7 +78,7 @@ export async function runProjectResultCommand(input: {
|
|
|
62
78
|
}
|
|
63
79
|
|
|
64
80
|
if (args.command === "history") {
|
|
65
|
-
const source = args.source === undefined ? undefined :
|
|
81
|
+
const source = args.source === undefined ? undefined : await historySourcePath(args.source);
|
|
66
82
|
const page = await browseBuildOutputHistory(repository, {
|
|
67
83
|
projectRoot,
|
|
68
84
|
output: args.outputName,
|
|
@@ -177,7 +193,7 @@ export async function runProjectResultCommand(input: {
|
|
|
177
193
|
kind: exported.kind,
|
|
178
194
|
path: exported.path,
|
|
179
195
|
};
|
|
180
|
-
const path = projectPath(exported.path, projectRoot);
|
|
196
|
+
const path = projectPath(await realpath(exported.path), projectRoot);
|
|
181
197
|
write(machine, `Exported ${exported.output} → ${path}`, "success",
|
|
182
198
|
args.presentation.verbose ? [
|
|
183
199
|
["Build", exported.build],
|
package/packages/cli/src/main.ts
CHANGED
|
@@ -148,7 +148,8 @@ export async function runCli(
|
|
|
148
148
|
}, "Runtime Profile created", "success", [
|
|
149
149
|
["Profile", selected.profile],
|
|
150
150
|
["Project", selected.projectRoot],
|
|
151
|
-
], ["
|
|
151
|
+
], ["Review the Profile’s credential stores and Endpoint settings before login or runtime up.",
|
|
152
|
+
"No package was installed, no service was contacted and no Worker was started."]);
|
|
152
153
|
return;
|
|
153
154
|
}
|
|
154
155
|
if (args.command === "runtime" && args.action === "use") {
|
|
@@ -28,6 +28,54 @@ function tokenExpiry(value: { readonly expires_at?: unknown; readonly expires_in
|
|
|
28
28
|
return seconds === undefined ? undefined : Date.now() + seconds * 1_000;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
export type AuthorizeBrowserLaunch = {
|
|
32
|
+
readonly command: string;
|
|
33
|
+
readonly args: readonly string[];
|
|
34
|
+
readonly windowsVerbatimArguments: boolean;
|
|
35
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Open the authorize URL in a browser. Windows `cmd /c start` treats `&` as a command
|
|
40
|
+
* separator unless the URL is a later quoted token after an explicit window title.
|
|
41
|
+
*/
|
|
42
|
+
export function authorizeBrowserLaunch(
|
|
43
|
+
url: string,
|
|
44
|
+
options: { readonly platform?: NodeJS.Platform; readonly comSpec?: string } = {},
|
|
45
|
+
): AuthorizeBrowserLaunch {
|
|
46
|
+
const platform = options.platform ?? process.platform;
|
|
47
|
+
if (platform === "win32") {
|
|
48
|
+
// Expand one environment value after cmd parses the command. Interpolating the
|
|
49
|
+
// URL into command text would also expand percent sequences inside the URL.
|
|
50
|
+
const serialized = new URL(url).href;
|
|
51
|
+
return {
|
|
52
|
+
command: options.comSpec ?? process.env.ComSpec ?? "cmd.exe",
|
|
53
|
+
args: ["/d", "/s", "/v:off", "/c", 'start "" "%HYPIT_OAUTH_AUTHORIZE_URL%"'],
|
|
54
|
+
windowsVerbatimArguments: true,
|
|
55
|
+
env: { HYPIT_OAUTH_AUTHORIZE_URL: serialized },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
command: platform === "darwin" ? "open" : "xdg-open",
|
|
60
|
+
args: [url],
|
|
61
|
+
windowsVerbatimArguments: false,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function openAuthorizeUrl(url: string): void {
|
|
66
|
+
const launch = authorizeBrowserLaunch(url);
|
|
67
|
+
const child = spawn(launch.command, [...launch.args], {
|
|
68
|
+
stdio: "ignore",
|
|
69
|
+
detached: true,
|
|
70
|
+
windowsHide: true,
|
|
71
|
+
windowsVerbatimArguments: launch.windowsVerbatimArguments,
|
|
72
|
+
...(launch.env === undefined ? {} : { env: { ...process.env, ...launch.env } }),
|
|
73
|
+
});
|
|
74
|
+
// A missing opener must not crash the CLI; the URL is already on the progress line.
|
|
75
|
+
child.once("error", () => undefined);
|
|
76
|
+
child.unref();
|
|
77
|
+
}
|
|
78
|
+
|
|
31
79
|
/** Acquire one OAuth credential from the exact data declared by its Endpoint package. */
|
|
32
80
|
export async function acquireOAuthCredential(
|
|
33
81
|
acquisition: CredentialAcquisition,
|
|
@@ -102,13 +150,8 @@ export async function acquireOAuthCredential(
|
|
|
102
150
|
authorize.searchParams.set("code_challenge", challenge);
|
|
103
151
|
authorize.searchParams.set("code_challenge_method", "S256");
|
|
104
152
|
options.onProgress?.(`Opening sign-in: ${authorize}`);
|
|
105
|
-
if (options.open === undefined)
|
|
106
|
-
|
|
107
|
-
const openerArgs = process.platform === "win32" ? ["/c", "start", "", authorize.toString()] : [authorize.toString()];
|
|
108
|
-
spawn(opener, openerArgs, { stdio: "ignore", detached: true, windowsHide: true }).unref();
|
|
109
|
-
} else {
|
|
110
|
-
options.open(authorize.toString());
|
|
111
|
-
}
|
|
153
|
+
if (options.open === undefined) openAuthorizeUrl(authorize.toString());
|
|
154
|
+
else options.open(authorize.toString());
|
|
112
155
|
const code = await callback;
|
|
113
156
|
options.onProgress?.("Authorization returned. Exchanging token…");
|
|
114
157
|
const deadline = AbortSignal.timeout(acquisition.requestTimeoutMs);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { relative, resolve } from "node:path";
|
|
1
|
+
import { relative, resolve, sep } from "node:path";
|
|
2
2
|
|
|
3
3
|
import type { CanonicalValue } from "@hypit/protocol";
|
|
4
4
|
|
|
@@ -279,7 +279,7 @@ function glyph(io: CliIo, unicode: string, ascii: string): string {
|
|
|
279
279
|
function shortPath(path: string): string {
|
|
280
280
|
const absolute = resolve(path);
|
|
281
281
|
const local = relative(process.cwd(), absolute);
|
|
282
|
-
return local.length > 0 && !local.startsWith(
|
|
282
|
+
return local.length > 0 && local !== ".." && !local.startsWith(`..${sep}`) ? local : absolute;
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
function facts(rows: readonly (readonly [string, string])[], colors: Palette): string[] {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile, realpath } from "node:fs/promises";
|
|
2
|
-
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
2
|
+
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
3
|
|
|
4
4
|
import { authorFrontendsFromHostFacets, prepareAuthorSource } from "@hypit/elaborator";
|
|
5
5
|
import type { AuthorFrontend } from "@hypit/elaborator";
|
|
@@ -12,7 +12,7 @@ import { parseSourceHeader, sourceFrontendPackageAbi } from "@hypit/source";
|
|
|
12
12
|
|
|
13
13
|
function isWithin(root: string, path: string): boolean {
|
|
14
14
|
const relation = relative(root, path);
|
|
15
|
-
return relation === "" || (!relation.startsWith(
|
|
15
|
+
return relation === "" || (relation !== ".." && !relation.startsWith(`..${sep}`) && !isAbsolute(relation));
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function selectedPackage(request: string): string {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# `@hypit/credential-store-file`
|
|
2
|
+
|
|
3
|
+
A writable CredentialStore for a deployment that explicitly chooses local file storage, including
|
|
4
|
+
Linux without an OS credential locker. It stores unencrypted credential values in an owner-private
|
|
5
|
+
directory outside the video project. On Windows the selected directory must have a private user ACL;
|
|
6
|
+
POSIX file modes do not set Windows ACLs. Choose `@hypit/credential-store-os` when using macOS Keychain
|
|
7
|
+
or Windows Credential Locker, or `@hypit/credential-store-env` for externally supplied, read-only values.
|
|
8
|
+
|
|
9
|
+
## Select it before login
|
|
10
|
+
|
|
11
|
+
After `hypit runtime init`, edit the created Profile before running `auth` or starting its Runtime.
|
|
12
|
+
Replace its credential-store selection and the chosen Endpoint's credential reference together:
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"credentials": {
|
|
17
|
+
"file": { "use": "@hypit/credential-store-file" }
|
|
18
|
+
},
|
|
19
|
+
"endpoints": {
|
|
20
|
+
"hypihub.default": {
|
|
21
|
+
"use": "@hypit/provider-hypihub",
|
|
22
|
+
"config": {
|
|
23
|
+
"baseUrl": "https://hypit.ai",
|
|
24
|
+
"apiKey": { "store": "file", "key": "hypihub.oauth" }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This is a Profile fragment: preserve its format, dataRoot, other Endpoints and bindings. For an
|
|
32
|
+
existing Profile, edit only the selected references and use `hypit runtime use <profile>` to select it.
|
|
33
|
+
The Endpoint still owns its acquisition flow; the Store only persists its result:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
hypit auth login hypihub.default
|
|
37
|
+
hypit auth status hypihub.default
|
|
38
|
+
hypit auth logout hypihub.default
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
No existing OS credential is migrated and no failed lookup tries another Store. A damaged document
|
|
42
|
+
can be replaced by login or deleted by logout without decoding the old value. Status and execution
|
|
43
|
+
report the read error rather than treating corruption as a missing credential.
|
|
44
|
+
|
|
45
|
+
## Storage
|
|
46
|
+
|
|
47
|
+
The default directory is `credentials` under the Host state root printed by `hypit paths`.
|
|
48
|
+
`config.path` explicitly selects a directory, resolving relative paths against that Host root.
|
|
49
|
+
Opening the adapter and reading an absent key do not create files. Keep this directory outside Git.
|
|
50
|
+
|
|
51
|
+
Each key has its own JSON file containing only `secret` and optional `expiresAt`, as defined by
|
|
52
|
+
CredentialValue. Filenames use reversible lower-case hex encoding of the key's UTF-16 bytes, with a
|
|
53
|
+
`key-` prefix; they are not hashes or secret encryption. This preserves case-sensitive key identity on
|
|
54
|
+
case-insensitive filesystems and keeps slashes and reserved names out of filesystem path syntax.
|
|
55
|
+
Filesystem path-length limits still apply; an overlong key fails rather than selecting another name.
|
|
56
|
+
|
|
57
|
+
Writes create an owner-only temporary file and replace the single destination with rename. Different
|
|
58
|
+
keys do not overwrite each other's updates; concurrent writes to the same key keep the last completed
|
|
59
|
+
replacement. The Store does not enumerate credentials, maintain an index, or record write history.
|
|
60
|
+
Permission and I/O errors propagate. Existing directory permissions are not silently changed.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hypit/credential-store-file",
|
|
3
|
+
"version": "0.0.0-dev",
|
|
4
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
5
|
+
"private": true,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"hypit": {
|
|
15
|
+
"activation": "./src/activation.ts"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@hypit/runtime": "workspace:*",
|
|
19
|
+
"@hypit/runtime-kit": "workspace:*"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { join, resolve } from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
createRuntimeCredentialStoreAdapterFacet,
|
|
4
|
+
runtimeConfigExact,
|
|
5
|
+
runtimeConfigObject,
|
|
6
|
+
runtimeConfigString,
|
|
7
|
+
} from "@hypit/runtime-kit";
|
|
8
|
+
import { FileCredentialStore } from "./store.js";
|
|
9
|
+
|
|
10
|
+
const fileCredentialStoreAdapter = createRuntimeCredentialStoreAdapterFacet({
|
|
11
|
+
use: "@hypit/credential-store-file",
|
|
12
|
+
validate(context) {
|
|
13
|
+
const config = runtimeConfigObject(context.config, "file CredentialStore");
|
|
14
|
+
runtimeConfigExact(config, ["path"], "file CredentialStore");
|
|
15
|
+
runtimeConfigString(config.path, "file credential path");
|
|
16
|
+
},
|
|
17
|
+
open(context) {
|
|
18
|
+
const config = runtimeConfigObject(context.config, "file CredentialStore");
|
|
19
|
+
const path = runtimeConfigString(config.path, "file credential path");
|
|
20
|
+
return { value: new FileCredentialStore(path === undefined
|
|
21
|
+
? join(context.hostStateRoot, "credentials") : resolve(context.hostStateRoot, path)) };
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const hypitPackage = {
|
|
26
|
+
format: "hypit.node-package@1" as const,
|
|
27
|
+
hostFacets: [fileCredentialStoreAdapter],
|
|
28
|
+
};
|
|
29
|
+
export default hypitPackage;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { FileCredentialStore } from "./store.js";
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, open, rename, rm, stat, unlink } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { verifyCredentialRef } from "@hypit/runtime";
|
|
5
|
+
import type { CredentialRef, CredentialValue, WritableCredentialStore } from "@hypit/runtime";
|
|
6
|
+
|
|
7
|
+
function missing(error: unknown): boolean {
|
|
8
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function credentialValue(value: unknown): CredentialValue {
|
|
12
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid credential value");
|
|
13
|
+
const record = value as Record<string, unknown>;
|
|
14
|
+
if (typeof record.secret !== "string" || record.secret.length === 0
|
|
15
|
+
|| (record.expiresAt !== undefined && (typeof record.expiresAt !== "number" || !Number.isFinite(record.expiresAt)))) {
|
|
16
|
+
throw new Error("invalid credential value");
|
|
17
|
+
}
|
|
18
|
+
return { secret: record.secret, ...(record.expiresAt === undefined ? {} : { expiresAt: record.expiresAt as number }) };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** One file per Store-local key; no enumeration, index or cross-key read/modify/write. */
|
|
22
|
+
export class FileCredentialStore implements WritableCredentialStore {
|
|
23
|
+
constructor(readonly directory: string) {}
|
|
24
|
+
|
|
25
|
+
owns(ref: CredentialRef): boolean { return ref.store === "file"; }
|
|
26
|
+
|
|
27
|
+
#path(ref: CredentialRef): string {
|
|
28
|
+
verifyCredentialRef(ref);
|
|
29
|
+
if (!this.owns(ref)) throw new Error(`File CredentialStore does not own ${ref.store}`);
|
|
30
|
+
// Reversible UTF-16 encoding preserves the opaque JS key, even on case-insensitive filesystems.
|
|
31
|
+
// The prefix avoids reserved OS filenames; no key characters become path separators.
|
|
32
|
+
return join(this.directory, `key-${Buffer.from(ref.key, "utf16le").toString("hex")}.json`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async #privateDirectory(create: boolean): Promise<void> {
|
|
36
|
+
if (create) await mkdir(this.directory, { recursive: true, mode: 0o700 });
|
|
37
|
+
const info = await stat(this.directory);
|
|
38
|
+
if (!info.isDirectory()) throw new Error(`Credential path is not a directory: ${this.directory}`);
|
|
39
|
+
if (process.platform !== "win32" && (info.mode & 0o077) !== 0) {
|
|
40
|
+
throw new Error(`Credential directory must be owner-private: ${this.directory}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async resolve(ref: CredentialRef): Promise<CredentialValue | undefined> {
|
|
45
|
+
if (!this.owns(ref)) return undefined;
|
|
46
|
+
const path = this.#path(ref);
|
|
47
|
+
try { await this.#privateDirectory(false); } catch (error) {
|
|
48
|
+
if (missing(error)) return undefined;
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
let file;
|
|
52
|
+
try { file = await open(path, "r"); } catch (error) {
|
|
53
|
+
if (missing(error)) return undefined;
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const info = await file.stat();
|
|
58
|
+
if (process.platform !== "win32" && (info.mode & 0o077) !== 0) {
|
|
59
|
+
throw new Error(`Credential file must be owner-private: ${path}`);
|
|
60
|
+
}
|
|
61
|
+
const text = await file.readFile("utf8");
|
|
62
|
+
// JSON parser messages may include secret bytes. Report the file, never its contents.
|
|
63
|
+
try { return credentialValue(JSON.parse(text)); } catch {
|
|
64
|
+
throw new Error(`Cannot decode credential file: ${path}; replace it with auth login or remove it with auth logout`);
|
|
65
|
+
}
|
|
66
|
+
} finally { await file.close(); }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async put(ref: CredentialRef, value: CredentialValue): Promise<void> {
|
|
70
|
+
const path = this.#path(ref);
|
|
71
|
+
const contents = JSON.stringify(credentialValue(value));
|
|
72
|
+
await this.#privateDirectory(true);
|
|
73
|
+
const temporary = join(this.directory, `.write-${randomUUID()}.tmp`);
|
|
74
|
+
const file = await open(temporary, "wx", 0o600);
|
|
75
|
+
try {
|
|
76
|
+
try { await file.writeFile(`${contents}\n`, "utf8"); } finally { await file.close(); }
|
|
77
|
+
await rename(temporary, path);
|
|
78
|
+
} finally { await rm(temporary, { force: true }); }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async delete(ref: CredentialRef): Promise<boolean> {
|
|
82
|
+
const path = this.#path(ref);
|
|
83
|
+
// Deletion never opens or decodes the old document.
|
|
84
|
+
try { await unlink(path); return true; } catch (error) {
|
|
85
|
+
if (missing(error)) return false;
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "@hypit/media";
|
|
10
10
|
import type { FontArtifactRef, FontStackRef } from "@hypit/media";
|
|
11
11
|
import type { StructuredElement, StructuredSurfaceHandler } from "@hypit/markup";
|
|
12
|
-
import { resolveNodePackageResource } from "@hypit/package-loader-node";
|
|
12
|
+
import { NodePackageNotFoundError, resolveNodePackageResource } from "@hypit/package-loader-node";
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
15
|
openFontFamilies,
|
|
@@ -21,11 +21,17 @@ import type {
|
|
|
21
21
|
OpenFontStyle,
|
|
22
22
|
} from "./catalog.js";
|
|
23
23
|
|
|
24
|
+
const fontDependencies = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")) as {
|
|
25
|
+
readonly optionalDependencies: Readonly<Record<string, string>>;
|
|
26
|
+
};
|
|
27
|
+
|
|
24
28
|
function resolveFontPackageFile(packageName: string, path: string): string {
|
|
25
29
|
try {
|
|
26
30
|
return resolveNodePackageResource(packageName, path, { from: import.meta.url });
|
|
27
31
|
} catch (error) {
|
|
28
|
-
|
|
32
|
+
if (!(error instanceof NodePackageNotFoundError)) throw error;
|
|
33
|
+
const version = fontDependencies.optionalDependencies[packageName];
|
|
34
|
+
if (version === undefined) throw error;
|
|
29
35
|
throw new Error(
|
|
30
36
|
`${packageName} is needed by this authored font. Install it once with: hypit packages install ${packageName}@${version}`,
|
|
31
37
|
{ cause: error },
|
|
@@ -12,6 +12,8 @@ This package understands only `VisualTrack`, `ProgramSpace` and canvas geometry.
|
|
|
12
12
|
does not know Caption, Speech, B-roll, Seedance or any other author-domain component. It flattens
|
|
13
13
|
every Track's Presents, orders them by their own absolute stacking keys and emits frame-bound local
|
|
14
14
|
keyframes. An authoring Track never becomes an isolated render stacking surface.
|
|
15
|
+
Terminal text and SVG mask sources retain the same Present-relative frame animations as other
|
|
16
|
+
elements, including direct seeks into the middle of a Present.
|
|
15
17
|
|
|
16
18
|
It deliberately ignores `AudioTrack`. HyperFrames produces a silent visual fact; the media pipeline
|
|
17
19
|
compiles and renders program audio separately, then an explicit mux Provider joins the two. Changing
|
|
@@ -23,6 +25,12 @@ a Provider can verify and name staged bytes without guessing from the hash. A lo
|
|
|
23
25
|
render Runtime calls `materializeHyperframesHtml()` with its own Artifact URL resolver before
|
|
24
26
|
handing the HTML to HyperFrames. That environment-specific materialization is not a new compiled
|
|
25
27
|
Record and does not change the compiled document.
|
|
28
|
+
Staging assigns short local filenames; opaque Resource IDs are never interpreted as filesystem
|
|
29
|
+
paths or filenames, so their length and punctuation do not restrict the host filesystem.
|
|
30
|
+
`stageHyperframesProject` streams each asset into its staged file. Its `validateSurface(surface, path,
|
|
31
|
+
signal)` callback borrows that completed file during inspection; it receives neither a whole-file
|
|
32
|
+
byte copy nor ownership of the file. The staging caller keeps the directory alive until all work
|
|
33
|
+
has settled, including cancellation.
|
|
26
34
|
|
|
27
35
|
The document exposes its render domain directly rather than asking an Endpoint to scrape HTML:
|
|
28
36
|
exact rational `frameRate`, integer `frameCount`, and canvas dimensions are explicit document
|
|
@@ -53,8 +61,12 @@ places a direct typed child. Every child is placed once, including sampled video
|
|
|
53
61
|
This keeps actual video available to the renderer's exact source-frame preparation.
|
|
54
62
|
|
|
55
63
|
CSS is scoped to the generated program root with `@scope`; `:scope` styles that root. The HTML can
|
|
56
|
-
contain arbitrary local structure, SVG, internal stacking, masks and backdrop filters.
|
|
57
|
-
|
|
64
|
+
contain arbitrary local structure, SVG, internal stacking, masks and backdrop filters.
|
|
65
|
+
CSS scope does not isolate HTML/SVG IDs. Use classes or data attributes for local queries; when an
|
|
66
|
+
SVG needs an ID for `url(#...)` or `href`, derive it from the unique `root.id` in `setup` and set both
|
|
67
|
+
the definition and its references there. Reusable instances must not repeat hard-coded SVG IDs.
|
|
68
|
+
|
|
69
|
+
The optional `setup` string is a JavaScript function body with `root` and `data` arguments. It returns a synchronous
|
|
58
70
|
`render(localFrame)` function, evaluated on initial load and active `hf-seek` events. Outside the
|
|
59
71
|
Present's lifetime, the dispatcher settles the boundary pose once instead of repeatedly updating
|
|
60
72
|
an invisible program. Active seeks always redraw, including the same frame after resources become
|
|
@@ -63,6 +75,8 @@ objects in `setup`; express animation state as a function of frame and inputs so
|
|
|
63
75
|
begin at any frame. Async work belongs to
|
|
64
76
|
material preparation before rendering; browser resources belong in the program's declared artifacts.
|
|
65
77
|
Use `hyperframesResourceUri(artifact.resource)` in resource-bearing markup or CSS.
|
|
78
|
+
Returning a Promise from `render` reports an authoring error; frame capture never waits for an
|
|
79
|
+
unbounded asynchronous drawing task or races it.
|
|
66
80
|
|
|
67
81
|
Ordinary child sampling follows the Present clock. Reframing the parent leaves source playback
|
|
68
82
|
unchanged. `projectTimelineMedia` from `@hypit/hypit/timeline` gives a component selected prepared
|
|
@@ -76,7 +76,12 @@ export function browserProgramScript(entries: readonly {
|
|
|
76
76
|
// including repeated frame 0 after images/fonts finish loading.
|
|
77
77
|
if (region !== 'active' && entry.region === region) continue;
|
|
78
78
|
try {
|
|
79
|
-
entry.render(Math.max(0, Math.min(entry.durationFrames, local)));
|
|
79
|
+
const result = entry.render(Math.max(0, Math.min(entry.durationFrames, local)));
|
|
80
|
+
if (result != null && typeof result.then === 'function') {
|
|
81
|
+
// Observe a later rejection, but never let asynchronous drawing race frame capture.
|
|
82
|
+
Promise.resolve(result).catch(fail);
|
|
83
|
+
throw new Error('Browser program render(localFrame) must be synchronous; prepare asynchronous resources before rendering.');
|
|
84
|
+
}
|
|
80
85
|
entry.region = region;
|
|
81
86
|
}
|
|
82
87
|
catch (error) { fail(error); }
|