@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
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { access, stat } from "node:fs/promises";
|
|
3
|
+
import { constants } from "node:fs";
|
|
4
|
+
import { delimiter, isAbsolute, resolve } from "node:path";
|
|
2
5
|
|
|
3
6
|
type ProcessResult = {
|
|
4
7
|
readonly stdout: Uint8Array;
|
|
@@ -13,6 +16,25 @@ export function positiveInteger(value: number, subject: string): number {
|
|
|
13
16
|
return value;
|
|
14
17
|
}
|
|
15
18
|
|
|
19
|
+
/** The engine's binary override requires a path; resolve our selected command without its fallback search. */
|
|
20
|
+
export async function mediaExecutablePath(value: string): Promise<string> {
|
|
21
|
+
const pathLike = isAbsolute(value) || value.includes("/") || value.includes("\\");
|
|
22
|
+
const bases = pathLike ? [resolve(value)]
|
|
23
|
+
: (process.env.PATH ?? "").split(delimiter).filter(Boolean).map(directory => resolve(directory, value));
|
|
24
|
+
const extensions = process.platform === "win32" && !/\.[^\\/]+$/u.test(value) ? [".exe", ".com", ""] : [""];
|
|
25
|
+
for (const base of bases) for (const extension of extensions) {
|
|
26
|
+
const candidate = `${base}${extension}`;
|
|
27
|
+
try {
|
|
28
|
+
if (!(await stat(candidate)).isFile()) continue;
|
|
29
|
+
await access(candidate, process.platform === "win32" ? constants.F_OK : constants.X_OK);
|
|
30
|
+
return candidate;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (!["ENOENT", "ENOTDIR", "EACCES"].includes((error as NodeJS.ErrnoException).code ?? "")) throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
throw new Error(`HyperFrames media executable ${value} is unavailable; correct the Provider's ffmpegPath or ffprobePath.`);
|
|
36
|
+
}
|
|
37
|
+
|
|
16
38
|
function processEnvironment(): NodeJS.ProcessEnv {
|
|
17
39
|
const names = ["PATH", "HOME", "TMPDIR", "LANG", "LC_ALL"] as const;
|
|
18
40
|
return Object.fromEntries(names.flatMap((name) => process.env[name] === undefined
|
|
@@ -1,54 +1,46 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { join } from "node:path";
|
|
2
3
|
|
|
3
4
|
import { probeMediaToolchain } from "@hypit/media-execution";
|
|
4
5
|
import type { ManagedProgram, ManagedProgramState } from "@hypit/runtime-kit";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return new Promise((resolve) => {
|
|
8
|
-
execFile(executable, [...args], { timeout: 15_000, shell: false, windowsHide: true }, (error, stdout, stderr) => {
|
|
9
|
-
resolve(error === null
|
|
10
|
-
? { ok: true, output: stdout.trim() }
|
|
11
|
-
: { ok: false, output: (stderr.trim() || error.message).split("\n").at(-1) ?? "" });
|
|
12
|
-
});
|
|
13
|
-
});
|
|
14
|
-
}
|
|
6
|
+
import { browserCacheDirectory, browserDownloadBaseUrl, browserDownloadUrl, browserExecutablePath, configuredBrowserPath, requireBrowserExecutable, selectedBrowserVersion } from "./browser.js";
|
|
7
|
+
import type { BrowserOptions } from "./browser.js";
|
|
15
8
|
|
|
16
9
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* when this Provider is selected.
|
|
10
|
+
* This Provider owns browser selection and preparation. Probes never install;
|
|
11
|
+
* rendering receives the same selected executable instead of invoking engine discovery.
|
|
20
12
|
*/
|
|
21
13
|
export function localHyperframesBrowserProgram(
|
|
22
|
-
input: {
|
|
14
|
+
input: BrowserOptions & {
|
|
23
15
|
readonly id: string;
|
|
24
16
|
readonly nodePath: string;
|
|
25
|
-
readonly hyperframesCliPath: string | (() => string);
|
|
26
17
|
readonly ffprobePath: string;
|
|
27
18
|
readonly ffmpegPath?: string;
|
|
28
19
|
},
|
|
29
20
|
): ManagedProgram {
|
|
30
|
-
const
|
|
21
|
+
const version = selectedBrowserVersion(input);
|
|
22
|
+
const selectedPath = browserExecutablePath(input);
|
|
23
|
+
const baseUrl = browserDownloadBaseUrl(input);
|
|
31
24
|
const probeBrowser = async (): Promise<ManagedProgramState> => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const located = await run(input.nodePath, [executable, "browser", "path"]);
|
|
35
|
-
if (!located.ok) return { state: "down", detail: `HyperFrames browser is unavailable: ${located.output}` };
|
|
36
|
-
const path = located.output.trim();
|
|
37
|
-
if (path.length === 0 || path.includes("\n") || path.includes("\r")) {
|
|
38
|
-
return { state: "mismatch", detail: "HyperFrames returned an invalid browser path" };
|
|
39
|
-
}
|
|
40
|
-
const version = await run(path, ["--version"]);
|
|
41
|
-
if (!version.ok || version.output.length === 0) {
|
|
42
|
-
return { state: "mismatch", detail: `HyperFrames browser cannot start: ${version.output}` };
|
|
43
|
-
}
|
|
25
|
+
try { await requireBrowserExecutable(selectedPath, version); }
|
|
26
|
+
catch (error) { return { state: "down", detail: error instanceof Error ? error.message : String(error) }; }
|
|
44
27
|
return { state: "ready" };
|
|
45
28
|
};
|
|
46
29
|
return {
|
|
47
30
|
id: input.id,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
31
|
+
...(configuredBrowserPath(input) === undefined ? {
|
|
32
|
+
// Projects sharing this installation also share the existing Program lifecycle lock/logs.
|
|
33
|
+
stateRoot: join(browserCacheDirectory(input), ".hypit-render-program"),
|
|
34
|
+
installation: {
|
|
35
|
+
probe: probeBrowser,
|
|
36
|
+
commands: [{ label: `Install Chrome Headless Shell ${version} at ${selectedPath} from ${browserDownloadUrl(input)}`, command: input.nodePath, args: [
|
|
37
|
+
"--import", import.meta.resolve("tsx"),
|
|
38
|
+
"--import", new URL("./capture-bootstrap.ts", import.meta.url).href,
|
|
39
|
+
fileURLToPath(new URL("./browser-install.ts", import.meta.url)), browserCacheDirectory(input), version!,
|
|
40
|
+
...(baseUrl === undefined ? [] : [baseUrl]),
|
|
41
|
+
] }],
|
|
42
|
+
},
|
|
43
|
+
} : {}),
|
|
52
44
|
async probe(): Promise<ManagedProgramState> {
|
|
53
45
|
const browser = await probeBrowser();
|
|
54
46
|
if (browser.state !== "ready") return browser;
|
|
@@ -2,7 +2,6 @@ import { defineEndpointPackage } from "@hypit/endpoint-kit";
|
|
|
2
2
|
import { mediaTypes } from "@hypit/media";
|
|
3
3
|
import { renderHyperframesCapabilities, verifyHyperframesVisualRequest } from "@hypit/render-hyperframes";
|
|
4
4
|
import { canonicalize } from "@hypit/protocol";
|
|
5
|
-
import { resolveNodePackageExecutable } from "@hypit/package-loader-node";
|
|
6
5
|
import { renderHyperframesVisual, resolveExecutionOptions, renderWorkerLimit } from "./render.js";
|
|
7
6
|
import type { HyperframesExecutionOptions } from "./options.js";
|
|
8
7
|
import { renderProgressReporter } from "./progress.js";
|
|
@@ -14,17 +13,12 @@ export type CreateLocalHyperframesProviderOptions = HyperframesExecutionOptions
|
|
|
14
13
|
readonly pool?: string;
|
|
15
14
|
/** Used by managed browser installation, not frame capture. */
|
|
16
15
|
readonly nodePath?: string;
|
|
17
|
-
readonly hyperframesCliPath?: string;
|
|
18
16
|
/** Whole render requests admitted concurrently; independent of frame workers. */
|
|
19
17
|
readonly defaultConcurrency?: number;
|
|
20
18
|
/** Shared Chrome slots across Need executions using this pool. */
|
|
21
19
|
readonly browserCapacity?: number;
|
|
22
20
|
};
|
|
23
21
|
|
|
24
|
-
export function defaultHyperframesCliPath(): string {
|
|
25
|
-
return resolveNodePackageExecutable("hyperframes", "hyperframes", { from: import.meta.url });
|
|
26
|
-
}
|
|
27
|
-
|
|
28
22
|
export function createLocalHyperframesProvider(config: CreateLocalHyperframesProviderOptions) {
|
|
29
23
|
const execution = resolveExecutionOptions(config);
|
|
30
24
|
const pool = config.pool ?? config.instance ?? "hyperframes.local";
|
|
@@ -62,7 +56,10 @@ export function createLocalHyperframesProvider(config: CreateLocalHyperframesPro
|
|
|
62
56
|
: request.range.endFrameExclusive - request.range.startFrame;
|
|
63
57
|
const progress = renderProgressReporter(context.reportProgress, frameCount);
|
|
64
58
|
try {
|
|
65
|
-
const visual = await renderHyperframesVisual(request, { ...config,
|
|
59
|
+
const visual = await renderHyperframesVisual(request, { ...config,
|
|
60
|
+
...(execution.chromePath === undefined ? { browserVersion: execution.browserVersion! } : { chromePath: execution.chromePath }),
|
|
61
|
+
browserCacheDirectory: execution.browserCacheDirectory,
|
|
62
|
+
workers: execution.workers, maxWorkers,
|
|
66
63
|
resources: context.resources, onProgress: progress.onProgress,
|
|
67
64
|
...(context.reportDiagnostic === undefined ? {} : { onDiagnostic: context.reportDiagnostic }) });
|
|
68
65
|
return { value: { kind: "inline", value: canonicalize(visual) } };
|
|
@@ -6,7 +6,7 @@ import type { EndpointInvocationContext } from "@hypit/endpoint-kit";
|
|
|
6
6
|
import { stageHyperframesProject } from "@hypit/hyperframes/project";
|
|
7
7
|
import { sealRenderedVisual } from "@hypit/media";
|
|
8
8
|
import type { MediaFrameRange, RenderedVisual } from "@hypit/media";
|
|
9
|
-
import {
|
|
9
|
+
import { verifyCompositableSurfaceFile } from "@hypit/media-execution";
|
|
10
10
|
import { verifyHyperframesVisualRequest } from "@hypit/render-hyperframes";
|
|
11
11
|
import type { HyperframesVisualRequest } from "@hypit/render-hyperframes";
|
|
12
12
|
import { isStreamingResourceStore } from "@hypit/runtime";
|
|
@@ -15,6 +15,7 @@ import { assert, positiveInteger } from "./process.js";
|
|
|
15
15
|
import { runCaptureProcess } from "./capture-process.js";
|
|
16
16
|
import { finished } from "node:stream/promises";
|
|
17
17
|
import { autoWorkerLimit } from "./concurrency.js";
|
|
18
|
+
import { browserCacheDirectory, browserExecutablePath, configuredBrowserPath, requireBrowserExecutable, selectedBrowserVersion } from "./browser.js";
|
|
18
19
|
|
|
19
20
|
export type HyperframesRenderProgress =
|
|
20
21
|
| { readonly phase: "staging" | "encoding" | "storing"; readonly elapsedMs: number }
|
|
@@ -43,6 +44,9 @@ export function resolveExecutionOptions(options: HyperframesExecutionOptions) {
|
|
|
43
44
|
const browserGpu = options.browserGpu ?? "hardware";
|
|
44
45
|
assert(["auto", "software", "hardware"].includes(browserGpu), "HyperFrames browserGpu is invalid");
|
|
45
46
|
return {
|
|
47
|
+
chromePath: configuredBrowserPath(options),
|
|
48
|
+
browserVersion: selectedBrowserVersion(options),
|
|
49
|
+
browserCacheDirectory: browserCacheDirectory(options),
|
|
46
50
|
workers,
|
|
47
51
|
maxWorkers: workers === "auto" ? positiveInteger(options.maxWorkers ?? autoWorkerLimit(), "maxWorkers") : workers,
|
|
48
52
|
quality, browserGpu,
|
|
@@ -71,7 +75,7 @@ export async function renderHyperframesVisual(
|
|
|
71
75
|
options: RenderHyperframesVisualOptions,
|
|
72
76
|
): Promise<RenderedVisual> {
|
|
73
77
|
verifyHyperframesVisualRequest(request);
|
|
74
|
-
const config = resolveExecutionOptions(options);
|
|
78
|
+
const config = { ...resolveExecutionOptions(options), chromePath: browserExecutablePath(options) };
|
|
75
79
|
const { document } = request;
|
|
76
80
|
const range = request.range ?? { startFrame: 0, endFrameExclusive: document.frameCount };
|
|
77
81
|
const frameCount = range.endFrameExclusive - range.startFrame;
|
|
@@ -97,8 +101,9 @@ export async function renderHyperframesVisual(
|
|
|
97
101
|
};
|
|
98
102
|
try {
|
|
99
103
|
signal.throwIfAborted();
|
|
104
|
+
await requireBrowserExecutable(config.chromePath, config.browserVersion);
|
|
100
105
|
await options.onDiagnostic?.({ level: "info", message:
|
|
101
|
-
`Render ${frameCount} frames; workers ${config.workers} (limit ${renderWorkerLimit(config, frameCount, document.frameRate.numerator / document.frameRate.denominator)}); opaque fast PNG; quality ${config.quality}; GPU ${config.browserGpu}; encoder ${config.ffmpegPath}` });
|
|
106
|
+
`Render ${frameCount} frames; workers ${config.workers} (limit ${renderWorkerLimit(config, frameCount, document.frameRate.numerator / document.frameRate.denominator)}); opaque fast PNG; quality ${config.quality}; GPU ${config.browserGpu}; browser ${config.chromePath}; encoder ${config.ffmpegPath}` });
|
|
102
107
|
options.onProgress?.({ phase: "staging", elapsedMs: 0 });
|
|
103
108
|
work = await mkdtemp(join(tmpdir(), "hypit-hyperframes-local-"));
|
|
104
109
|
await stageHyperframesProject({ document, directory: work, signal,
|
|
@@ -109,7 +114,7 @@ export async function renderHyperframesVisual(
|
|
|
109
114
|
assert(bytes !== undefined, `HyperFrames Artifact ${artifact.resource} is unavailable`);
|
|
110
115
|
return bytes;
|
|
111
116
|
},
|
|
112
|
-
validateSurface: (surface,
|
|
117
|
+
validateSurface: (surface, path, probeSignal) => verifyCompositableSurfaceFile({ surface, path,
|
|
113
118
|
ffprobePath: config.ffprobePath, processTimeoutMs: config.processTimeoutMs,
|
|
114
119
|
maxProbeOutputBytes: config.maxProcessOutputBytes, signal: probeSignal! }),
|
|
115
120
|
});
|
|
@@ -118,6 +118,9 @@ Resource identity with the same declared person-reference classification is uplo
|
|
|
118
118
|
cross-Build cache. Seedance visual references can carry `personReference` in their media fields;
|
|
119
119
|
the mapping declares it as a resource-transport field and the upload session receives
|
|
120
120
|
`is_person_reference`, preserving true, false and omission. It stays out of the generation body.
|
|
121
|
+
This covers reference images, reference videos, and first/last frames for every declared Seedance
|
|
122
|
+
variant. Omission remains absent on the wire; HypiHub's upload API currently defaults it to false,
|
|
123
|
+
so omission does not enable detection or person-reference preparation.
|
|
121
124
|
HypiHub stores the authored classification and prepares the applicable upstream person reference;
|
|
122
125
|
this Provider does not detect faces or select an upstream private-avatar group.
|
|
123
126
|
|
|
@@ -53,3 +53,8 @@ to start execution, then `const runtime = await host.createRuntime(); await runt
|
|
|
53
53
|
to submit work. `RuntimeHostExecution` does not run a second embedded Worker loop. A local carrier
|
|
54
54
|
may stop accepting new Builds and drain; its assigned Builds stay in place and all carriers continue
|
|
55
55
|
using the same resource accounting. Process policy belongs to the local Runtime, not this Host ABI.
|
|
56
|
+
|
|
57
|
+
`prepareHostPackages` accepts exact specifier strings or `{ specifier, env }` installation inputs.
|
|
58
|
+
The optional environment is applied only to that npm child process, merged over its inherited
|
|
59
|
+
environment. It is neither persisted as installation status nor returned in package reports.
|
|
60
|
+
The shared installer does not interpret SDK-specific variables; their owning packages supply them.
|
|
@@ -70,17 +70,20 @@ export type BuildView = {
|
|
|
70
70
|
}[];
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
-
export type
|
|
73
|
+
export type RuntimeHostCredentialDescription = {
|
|
74
74
|
readonly endpoint: string;
|
|
75
75
|
readonly slot: string;
|
|
76
76
|
readonly label: string;
|
|
77
77
|
readonly kind: "secret" | "json";
|
|
78
78
|
readonly ref: CredentialRef;
|
|
79
79
|
readonly acquisition?: CredentialAcquisition;
|
|
80
|
-
readonly configured: boolean;
|
|
81
80
|
readonly writable: boolean;
|
|
82
81
|
};
|
|
83
82
|
|
|
83
|
+
export type RuntimeHostCredentialStatus = RuntimeHostCredentialDescription & {
|
|
84
|
+
readonly configured: boolean;
|
|
85
|
+
};
|
|
86
|
+
|
|
84
87
|
export type RuntimeHostControl = {
|
|
85
88
|
/** Build evidence while active. Finished evidence is read through its Result Repository. */
|
|
86
89
|
logs?(build: string, lines: number): Promise<import("@hypit/runtime").ExecutionLogView | undefined>;
|
|
@@ -105,6 +108,8 @@ export type RuntimeHostResultControl = {
|
|
|
105
108
|
};
|
|
106
109
|
|
|
107
110
|
export type RuntimeHostCredentialControl = {
|
|
111
|
+
/** Endpoint declarations and Store write capability; never reads an existing secret. */
|
|
112
|
+
describeCredentials(endpoint?: string): Promise<readonly RuntimeHostCredentialDescription[]>;
|
|
108
113
|
credentials(endpoint?: string): Promise<readonly RuntimeHostCredentialStatus[]>;
|
|
109
114
|
putCredential(endpoint: string, slot: string, secret: string): Promise<RuntimeHostCredentialStatus>;
|
|
110
115
|
deleteCredential(endpoint: string, slot: string): Promise<{
|
|
@@ -73,7 +73,7 @@ export async function inspectHostPackage(
|
|
|
73
73
|
: undefined;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
async function runNpm(root: string, specifiers: readonly string[], logPath: string): Promise<void> {
|
|
76
|
+
async function runNpm(root: string, specifiers: readonly string[], logPath: string, env?: Readonly<Record<string, string>>): Promise<void> {
|
|
77
77
|
// npm's prefix and cwd must denote the same physical project (not /tmp vs /private/tmp).
|
|
78
78
|
const cwd = await realpath(root);
|
|
79
79
|
const npmArgs = [
|
|
@@ -96,6 +96,7 @@ async function runNpm(root: string, specifiers: readonly string[], logPath: stri
|
|
|
96
96
|
cwd,
|
|
97
97
|
shell: false,
|
|
98
98
|
windowsHide: true,
|
|
99
|
+
env: { ...process.env, ...env },
|
|
99
100
|
stdio: ["ignore", log.fd, log.fd],
|
|
100
101
|
});
|
|
101
102
|
child.on("error", (error) => reject(new Error(`Cannot start npm: ${error.message}. Log: ${logPath}`, { cause: error })));
|
|
@@ -114,21 +115,26 @@ async function runNpm(root: string, specifiers: readonly string[], logPath: stri
|
|
|
114
115
|
* package.json, lockfile and dependencies; Hypit keeps no parallel inventory.
|
|
115
116
|
*/
|
|
116
117
|
export async function prepareHostPackages(
|
|
117
|
-
specifiers: readonly string[],
|
|
118
|
+
specifiers: readonly (string | { readonly specifier: string; readonly env?: Readonly<Record<string, string>> })[],
|
|
118
119
|
options: {
|
|
119
120
|
readonly root: string;
|
|
120
121
|
readonly onProgress?: (event: HostPackageProgress) => void;
|
|
121
122
|
},
|
|
122
123
|
): Promise<readonly HostPackageReport[]> {
|
|
123
124
|
const root = resolve(options.root);
|
|
124
|
-
const bySpecifier = new Map<string, RegistryPackageSpec>();
|
|
125
|
+
const bySpecifier = new Map<string, RegistryPackageSpec & { readonly env?: Readonly<Record<string, string>> }>();
|
|
125
126
|
for (const specifier of specifiers) {
|
|
126
|
-
const parsed = parseRegistryPackageSpec(specifier);
|
|
127
|
-
bySpecifier.
|
|
127
|
+
const parsed = parseRegistryPackageSpec(typeof specifier === "string" ? specifier : specifier.specifier);
|
|
128
|
+
const env = { ...bySpecifier.get(parsed.specifier)?.env };
|
|
129
|
+
for (const [key, value] of Object.entries(typeof specifier === "string" ? {} : specifier.env ?? {})) {
|
|
130
|
+
if (env[key] !== undefined && env[key] !== value) throw new Error(`Conflicting installation environment ${key} for ${parsed.specifier}`);
|
|
131
|
+
env[key] = value;
|
|
132
|
+
}
|
|
133
|
+
bySpecifier.set(parsed.specifier, { ...parsed, ...(Object.keys(env).length === 0 ? {} : { env }) });
|
|
128
134
|
}
|
|
129
135
|
const required = [...bySpecifier.values()].sort((left, right) => left.specifier.localeCompare(right.specifier));
|
|
130
136
|
const reports: HostPackageReport[] = [];
|
|
131
|
-
for (const item of required) {
|
|
137
|
+
for (const { env, ...item } of required) {
|
|
132
138
|
const installation = externalPackageInstallRoot(root, item.name, item.version);
|
|
133
139
|
options.onProgress?.({ ...item, phase: "checking" });
|
|
134
140
|
const missing = await installedVersion(installation, item.name) !== item.version;
|
|
@@ -142,7 +148,7 @@ export async function prepareHostPackages(
|
|
|
142
148
|
dependencies: { [item.name]: item.version },
|
|
143
149
|
}, null, 2)}\n`, "utf8");
|
|
144
150
|
options.onProgress?.({ ...item, phase: "installing", logPath });
|
|
145
|
-
await runNpm(installation, [item.specifier], logPath);
|
|
151
|
+
await runNpm(installation, [item.specifier], logPath, env);
|
|
146
152
|
}
|
|
147
153
|
const version = await installedVersion(installation, item.name);
|
|
148
154
|
if (version !== item.version) {
|
|
@@ -258,3 +258,13 @@ capacity release and every completed Result. They use the production execution p
|
|
|
258
258
|
This is a load experiment rather than part of every package regression. It reports fixture preparation,
|
|
259
259
|
local progress, completion, executor RSS and cleanup separately; total test time also includes creating
|
|
260
260
|
and removing the temporary Result repositories. Neither suite uses a completion-time performance target.
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
### Credential management without reading the old value
|
|
264
|
+
|
|
265
|
+
`openCredentials(endpoint)` opens only the selected Endpoint's credential control.
|
|
266
|
+
`describeCredentials(endpoint)` returns its declared slots and each Store's write capability without
|
|
267
|
+
resolving secrets. `credentials(endpoint)` also reads current values to report status and propagates
|
|
268
|
+
read failures. Login and logout use the former: a damaged old credential cannot prevent replacement
|
|
269
|
+
or deletion. Successful writes and deletions report their operation's result without rereading the
|
|
270
|
+
secret. Execution still resolves credentials normally and reports Store errors.
|
|
@@ -462,7 +462,7 @@ export async function prepareRuntimeConfigPackages(
|
|
|
462
462
|
...document.endpoints.map((item) => item.use),
|
|
463
463
|
...credentials.credentials.map((item) => item.use),
|
|
464
464
|
], options.distributionPackageRoot);
|
|
465
|
-
return await prepareHostPackages(requirements
|
|
465
|
+
return await prepareHostPackages(requirements, {
|
|
466
466
|
root: hypitHostPackageRoot(options.hostStateRoot),
|
|
467
467
|
...(options.onProgress === undefined ? {} : { onProgress: options.onProgress }),
|
|
468
468
|
});
|
|
@@ -28,16 +28,21 @@ export function createLocalCredentialControl(
|
|
|
28
28
|
: `Endpoint ${endpoint} repeats credential slot ${slot}`);
|
|
29
29
|
return matches[0]!;
|
|
30
30
|
};
|
|
31
|
-
const
|
|
31
|
+
const describe = async (item: typeof descriptions[number]) => ({
|
|
32
32
|
...structuredClone(item),
|
|
33
|
-
configured: await options.credentialStore.resolve(item.ref) !== undefined,
|
|
34
33
|
writable: await writableCredentialStore(options.credentialStore, item.ref) !== undefined,
|
|
35
34
|
});
|
|
36
35
|
|
|
37
36
|
return {
|
|
37
|
+
async describeCredentials(endpoint) {
|
|
38
|
+
return await Promise.all(descriptions.filter((item) => endpoint === undefined || item.endpoint === endpoint).map(describe));
|
|
39
|
+
},
|
|
38
40
|
async credentials(endpoint) {
|
|
39
41
|
const selected = descriptions.filter((item) => endpoint === undefined || item.endpoint === endpoint);
|
|
40
|
-
return await Promise.all(selected.map(
|
|
42
|
+
return await Promise.all(selected.map(async (item) => ({
|
|
43
|
+
...await describe(item),
|
|
44
|
+
configured: await options.credentialStore.resolve(item.ref) !== undefined,
|
|
45
|
+
})));
|
|
41
46
|
},
|
|
42
47
|
async putCredential(endpoint, slot, secret) {
|
|
43
48
|
assert(secret.length > 0, "credential secret is empty");
|
|
@@ -45,14 +50,14 @@ export function createLocalCredentialControl(
|
|
|
45
50
|
const store = await writableCredentialStore(options.credentialStore, item.ref);
|
|
46
51
|
assert(store !== undefined, `CredentialStore ${item.ref.store} is not writable`);
|
|
47
52
|
await store.put(item.ref, { secret });
|
|
48
|
-
return
|
|
53
|
+
return { ...structuredClone(item), writable: true, configured: true };
|
|
49
54
|
},
|
|
50
55
|
async deleteCredential(endpoint, slot) {
|
|
51
56
|
const item = credential(endpoint, slot);
|
|
52
57
|
const store = await writableCredentialStore(options.credentialStore, item.ref);
|
|
53
58
|
assert(store !== undefined, `CredentialStore ${item.ref.store} is not writable`);
|
|
54
59
|
const deleted = await store.delete(item.ref);
|
|
55
|
-
return { deleted, credential:
|
|
60
|
+
return { deleted, credential: { ...structuredClone(item), writable: true, configured: false } };
|
|
56
61
|
},
|
|
57
62
|
close() {
|
|
58
63
|
return options.close?.();
|
|
@@ -41,7 +41,7 @@ function nonNegativeInteger(value: number, subject: string): number {
|
|
|
41
41
|
function projectPath(root: string, path: string): string {
|
|
42
42
|
const absolute = isAbsolute(path) ? resolve(path) : resolve(root, path);
|
|
43
43
|
const relation = relative(resolve(root), absolute);
|
|
44
|
-
assert(relation === "" || (!relation.startsWith(
|
|
44
|
+
assert(relation === "" || (relation !== ".." && !relation.startsWith(`..${sep}`) && !isAbsolute(relation)),
|
|
45
45
|
`Build Result source ${path} is outside project ${resolve(root)}`);
|
|
46
46
|
return (relation || ".").split(sep).join("/");
|
|
47
47
|
}
|
|
@@ -18,6 +18,20 @@ the author's literal, in whole seconds inside the model's declared range; measur
|
|
|
18
18
|
with `hypit measure` and write the number here. Nothing in the graph computes it, so a Build plan is
|
|
19
19
|
complete before it starts.
|
|
20
20
|
|
|
21
|
+
## Reference audio
|
|
22
|
+
|
|
23
|
+
The Seedance package rejects reference audio declared as `audio/mp4` or `audio/x-m4a`. Convert the
|
|
24
|
+
audio to WAV or MP3 before using it; `media:ExtractAudio` produces WAV and can consume an upstream
|
|
25
|
+
component's media output. Renaming a file or changing its declared media type is not conversion.
|
|
26
|
+
|
|
27
|
+
Known imports are checked during Surface decoding. Future audio stays a graph input and is checked
|
|
28
|
+
when its Blob arrives. The same rule applies to drafts, complete requests, planning and direct
|
|
29
|
+
generation Producers. `sealSeedanceRequest` uses the endpoint's model-aware request builder;
|
|
30
|
+
`seedanceComponent` and `seedanceDefinition.component` expose the same implementation.
|
|
31
|
+
|
|
32
|
+
This checks the Blob's declared media type, not its bytes or codec. The selected Provider remains
|
|
33
|
+
responsible for any additional service-specific input limits.
|
|
34
|
+
|
|
21
35
|
## Visual reference metadata
|
|
22
36
|
|
|
23
37
|
Declare whether each image or video contains a person/avatar reference, including an AI-generated
|
|
@@ -37,10 +51,38 @@ Inspect the actual reference when deciding the value. For `FrameVideo`, use
|
|
|
37
51
|
`first-frame-person-reference` and `last-frame-person-reference` beside their respective frame
|
|
38
52
|
inputs. A last-frame classification requires a last-frame input.
|
|
39
53
|
|
|
54
|
+
| Supplied visual input | Authored attribute | Request port |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| Each `Reference image={...}` | `person-reference` | `referenceImage` |
|
|
57
|
+
| Each `Reference video={...}` | `person-reference` | `referenceVideo` |
|
|
58
|
+
| `FrameVideo` first frame | `first-frame-person-reference` | `firstFrame` |
|
|
59
|
+
| `FrameVideo` last frame | `last-frame-person-reference` | `lastFrame` |
|
|
60
|
+
|
|
61
|
+
These forms apply to `standard`, `fast`, `mini` and `2.5`. For example:
|
|
62
|
+
|
|
63
|
+
```xml
|
|
64
|
+
<seedance:FrameVideo id="turn" model="fast" prompt={direction} duration="5"
|
|
65
|
+
first-frame={presenter.image} first-frame-person-reference="true"
|
|
66
|
+
last-frame={empty-room.image} last-frame-person-reference="false"/>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Classify the material supplied to this request, not the intended result. A dance video with a person
|
|
70
|
+
still needs `true` when used only for motion, even if the prompt asks for a different performer.
|
|
71
|
+
An empty room stays `false` when the prompt asks to add a person. Inspect video across the selected
|
|
72
|
+
excerpt, not only its opening frame. This flag neither detects faces nor locks or names an identity.
|
|
73
|
+
Identity and action direction remain in the prompt and references.
|
|
74
|
+
|
|
75
|
+
The SVML author declares this parameter on each reference input. Admitted files, generated
|
|
76
|
+
images/videos and reused Results use the same attributes. For a future output, declare the intended
|
|
77
|
+
reference classification explicitly; if its contents are uncertain, generate and inspect that
|
|
78
|
+
material before using it downstream.
|
|
79
|
+
|
|
40
80
|
The model's media ports carry this as `fields.personReference`. Providers interpret it through their
|
|
41
81
|
service's media handling; it is not a prompt sentence or a Core-level identity. HypiHub sends it as
|
|
42
82
|
`is_person_reference` when uploading the file, then uses the returned URL in the ordinary video
|
|
43
83
|
request. A project Provider maps it according to its own API.
|
|
84
|
+
Omission does not request automatic face detection. HypiHub currently treats omitted upload flags
|
|
85
|
+
as unmarked (`false`); declare `true` explicitly for a person reference that needs its preparation.
|
|
44
86
|
|
|
45
87
|
Video references can carry motion or camera behavior while image references carry the target
|
|
46
88
|
appearance. Request duration and reference-clip duration are different limits. Check the selected
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
sealGenerationPortRequest,
|
|
3
|
-
sealGenerationPortTable,
|
|
4
|
-
} from "@hypit/generation";
|
|
1
|
+
import { sealGenerationPortTable } from "@hypit/generation";
|
|
5
2
|
import type {
|
|
6
3
|
GenerationPortTable,
|
|
7
4
|
GenerationPortValue,
|
|
@@ -12,6 +9,7 @@ import type { SurfaceAttributeVocabulary, SurfacePortVocabulary } from "@hypit/m
|
|
|
12
9
|
import { defineExactModelModule } from "@hypit/model-kit";
|
|
13
10
|
import { textTypes } from "@hypit/text";
|
|
14
11
|
import type { ResourceId } from "@hypit/protocol";
|
|
12
|
+
import { validateSeedanceInputs } from "./validation.js";
|
|
15
13
|
|
|
16
14
|
export const seedanceModuleRef = { name: "@hypit/seedance", version: "1" } as const;
|
|
17
15
|
export const seedanceModels = ["seedance-2", "seedance-2-fast", "seedance-2-mini", "seedance-2.5"] as const;
|
|
@@ -93,7 +91,7 @@ export const seedancePorts: Readonly<Record<SeedanceModel, GenerationPortTable>>
|
|
|
93
91
|
export type SeedancePortMap = Readonly<Record<string, readonly GenerationPortValue[]>>;
|
|
94
92
|
|
|
95
93
|
export function sealSeedanceRequest(model: SeedanceModel, ports: SeedancePortMap): GenerationRequest {
|
|
96
|
-
return
|
|
94
|
+
return seedanceEndpointsByModel[model].sealRequest(ports);
|
|
97
95
|
}
|
|
98
96
|
|
|
99
97
|
const seedanceBaseDefinition = defineExactModelModule({
|
|
@@ -110,6 +108,7 @@ const seedanceBaseDefinition = defineExactModelModule({
|
|
|
110
108
|
: `${model.split("-").map((part) => part[0]!.toUpperCase() + part.slice(1)).join("")}Request`,
|
|
111
109
|
producerName: `request-${model}`,
|
|
112
110
|
ports: seedancePorts[model],
|
|
111
|
+
validateInputs: validateSeedanceInputs,
|
|
113
112
|
})),
|
|
114
113
|
});
|
|
115
114
|
|
|
@@ -327,23 +326,7 @@ export const seedanceMarkupSurfaces = [
|
|
|
327
326
|
/** The duration is an author literal on every Seedance Surface, so the manifest is the exact-model module's own. */
|
|
328
327
|
export const seedanceManifest = seedanceBaseDefinition.manifest;
|
|
329
328
|
|
|
330
|
-
const
|
|
331
|
-
.map((endpoint) => endpoint.mediaBindings["referenceAudio"]!.producer.name));
|
|
332
|
-
|
|
333
|
-
export const seedanceComponent = {
|
|
334
|
-
...seedanceBaseDefinition.component,
|
|
335
|
-
producers: seedanceBaseDefinition.component.producers.map((facet) =>
|
|
336
|
-
referenceAudioProducers.has(facet.producer.name) ? {
|
|
337
|
-
...facet,
|
|
338
|
-
handler: (context: Parameters<typeof facet.handler>[0]) => {
|
|
339
|
-
const artifact = context.inputs.artifact?.value;
|
|
340
|
-
if (artifact?.kind === "blob" && ["audio/mp4", "audio/x-m4a"].includes(artifact.mediaType)) {
|
|
341
|
-
throw new Error("Seedance reference audio does not accept m4a; convert to wav or mp3");
|
|
342
|
-
}
|
|
343
|
-
return facet.handler(context);
|
|
344
|
-
},
|
|
345
|
-
} : facet),
|
|
346
|
-
};
|
|
329
|
+
export const seedanceComponent = seedanceBaseDefinition.component;
|
|
347
330
|
export const seedanceDefinition = seedanceBaseDefinition;
|
|
348
331
|
|
|
349
332
|
export {
|
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
import { createSeedanceAssembledGenerationFragment } from "./fragment.js";
|
|
24
24
|
import { seedanceEndpoints, seedancePorts } from "./index.js";
|
|
25
25
|
import type { SeedanceModel, SeedancePortMap } from "./index.js";
|
|
26
|
+
import { validateSeedanceAudio } from "./validation.js";
|
|
26
27
|
|
|
27
28
|
type MediaInput = {
|
|
28
29
|
readonly port: "referenceImage" | "referenceVideo" | "referenceAudio" | "firstFrame" | "lastFrame";
|
|
@@ -109,9 +110,7 @@ function mediaReference(
|
|
|
109
110
|
if (value !== undefined && (value.kind !== "blob" || !value.mediaType.startsWith(`${role}/`))) {
|
|
110
111
|
throw new Error(`${subject} must reference ${role} media`);
|
|
111
112
|
}
|
|
112
|
-
if (value
|
|
113
|
-
throw new Error(`${subject} references m4a audio, which Seedance does not accept; convert to wav or mp3 and admit that file`);
|
|
114
|
-
}
|
|
113
|
+
if (value?.kind === "blob" && role === "audio") validateSeedanceAudio(value, subject);
|
|
115
114
|
return reference;
|
|
116
115
|
}
|
|
117
116
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { GenerationRequestDraft } from "@hypit/generation";
|
|
2
|
+
import type { BlobRef } from "@hypit/protocol";
|
|
3
|
+
|
|
4
|
+
/** Seedance's reference-audio rule, shared by known imports and request assembly. */
|
|
5
|
+
export function validateSeedanceAudio(artifact: BlobRef, subject = "Seedance reference audio"): void {
|
|
6
|
+
if (artifact.mediaType === "audio/mp4" || artifact.mediaType === "audio/x-m4a") {
|
|
7
|
+
throw new Error(`${subject} does not accept m4a; convert the reference audio to WAV or MP3 before using it (media:ExtractAudio produces WAV)`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Missing references in a draft remain graph inputs; only supplied audio is checked. */
|
|
12
|
+
export function validateSeedanceInputs(request: GenerationRequestDraft): void {
|
|
13
|
+
for (const value of request.ports.referenceAudio ?? []) {
|
|
14
|
+
if (typeof value === "object" && value.role === "audio") validateSeedanceAudio(value.artifact);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -47,7 +47,12 @@ function shim(): string {
|
|
|
47
47
|
start: parseFloat(ownStart || (present && present.getAttribute('data-start')) || '0') || 0,
|
|
48
48
|
duration: parseFloat(ownDuration || (present && present.getAttribute('data-duration')) || '0') || 0,
|
|
49
49
|
mediaStart: parseFloat(element.getAttribute('data-media-start') || '0') || 0,
|
|
50
|
-
rate: parseFloat(element.getAttribute('data-playback-rate') || '1') || 1
|
|
50
|
+
rate: parseFloat(element.getAttribute('data-playback-rate') || '1') || 1,
|
|
51
|
+
startFrame: Number(element.getAttribute('data-hypit-start-frame')),
|
|
52
|
+
endFrame: Number(element.getAttribute('data-hypit-end-frame')),
|
|
53
|
+
sourceFrame: element.getAttribute('data-hypit-source-frame').split('/').map(BigInt),
|
|
54
|
+
sourceRate: element.getAttribute('data-hypit-source-rate').split('/').map(BigInt),
|
|
55
|
+
sourceFps: element.getAttribute('data-hypit-source-fps').split('/').map(Number)
|
|
51
56
|
});
|
|
52
57
|
}
|
|
53
58
|
var audioContext;
|
|
@@ -149,7 +154,8 @@ function shim(): string {
|
|
|
149
154
|
}
|
|
150
155
|
for (var record of media) {
|
|
151
156
|
var local = currentSeconds - record.start;
|
|
152
|
-
var
|
|
157
|
+
var programFrame = Math.round(currentSeconds * fps);
|
|
158
|
+
var inside = programFrame >= record.startFrame && programFrame < record.endFrame;
|
|
153
159
|
var element = record.element;
|
|
154
160
|
element.style.visibility = inside ? 'visible' : 'hidden';
|
|
155
161
|
// Normalized picture Artifacts are deliberately silent. Programme sound
|
|
@@ -157,10 +163,18 @@ function shim(): string {
|
|
|
157
163
|
element.muted = true;
|
|
158
164
|
if (!inside) { element.pause(); continue; }
|
|
159
165
|
var target = record.mediaStart + (local + frameSeconds / 2) * record.rate;
|
|
166
|
+
var discrete = scrubbing || programFrame === record.endFrame - 1;
|
|
167
|
+
if (discrete) {
|
|
168
|
+
// The renderer floors exact source-frame coordinates. The midpoint of a
|
|
169
|
+
// programme frame can cross that source boundary at fractional speeds.
|
|
170
|
+
var a = record.sourceFrame, r = record.sourceRate;
|
|
171
|
+
var sourceFrame = Number((a[0] * r[1] + BigInt(programFrame - record.startFrame) * r[0] * a[1]) / (a[1] * r[1]));
|
|
172
|
+
target = (sourceFrame + 0.5) * record.sourceFps[1] / record.sourceFps[0];
|
|
173
|
+
}
|
|
160
174
|
if (Number.isFinite(element.duration) && element.duration > 0) {
|
|
161
175
|
target = Math.min(target, Math.max(0, element.duration - 0.001));
|
|
162
176
|
}
|
|
163
|
-
if (
|
|
177
|
+
if (discrete) {
|
|
164
178
|
element.pause();
|
|
165
179
|
waits.push(seekDecoded(element, target));
|
|
166
180
|
} else {
|
|
@@ -2,7 +2,7 @@ import { watch } from "node:fs";
|
|
|
2
2
|
import type { FSWatcher } from "node:fs";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
|
-
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
5
|
+
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { Readable } from "node:stream";
|
|
7
7
|
import { pipeline } from "node:stream/promises";
|
|
8
8
|
|
|
@@ -207,7 +207,7 @@ export function studioPlugin(options: StudioPluginOptions): Plugin {
|
|
|
207
207
|
if (isAbsolute(patch.path)) throw new Error("Studio patches must use workspace-relative paths.");
|
|
208
208
|
const absolute = resolve(options.workspaceRoot, patch.path);
|
|
209
209
|
const rel = relative(options.workspaceRoot, absolute);
|
|
210
|
-
if (rel.startsWith(
|
|
210
|
+
if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel) || !allowedSourceFiles.has(absolute)) {
|
|
211
211
|
throw new Error(`Studio cannot write source file ${patch.path}.`);
|
|
212
212
|
}
|
|
213
213
|
if (!Number.isInteger(patch.range.start) || !Number.isInteger(patch.range.end)
|
|
@@ -34,7 +34,11 @@ an editable starter; its Endpoint entries describe available routes, not choices
|
|
|
34
34
|
Keep an existing chosen service, or configure the chosen local or hosted Provider and its capability
|
|
35
35
|
bindings. HypiHub is the recommended integrated hosted route in the official Distribution; other
|
|
36
36
|
services use project Provider packages. If the user chooses HypiHub,
|
|
37
|
-
`hypit auth login hypihub.default` connects that account.
|
|
37
|
+
`hypit auth login hypihub.default` connects that account after choosing its CredentialStore.
|
|
38
|
+
The starter selects the OS store for macOS Keychain / Windows Credential Locker. On Linux, or when
|
|
39
|
+
explicitly choosing file storage, edit the Profile's `credentials` and Endpoint reference as shown
|
|
40
|
+
in [file CredentialStore](../credential-store-file/README.md#select-it-before-login) before `auth` or
|
|
41
|
+
`runtime up`. This selection is configuration; execution never switches stores automatically.
|
|
38
42
|
`hypit doctor --endpoint <name>` checks a selected Endpoint;
|
|
39
43
|
`hypit runtime up --endpoint <name>` prepares that Endpoint and starts the Worker. Repeat the flag
|
|
40
44
|
for several chosen Endpoints; omitting it prepares the whole Profile. `hypit programs up --endpoint
|