@hypit/hypit 0.1.10 → 0.1.12

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 CHANGED
@@ -76,7 +76,7 @@ The `/hypit` skill is available to coding agents. Start a session in any empty o
76
76
  directory and ask it to create videos for you:
77
77
 
78
78
  ```text
79
- /hypit Clone this video: /path/to/video
79
+ /hypit Clone this video: /path/to/video.mp4, and replace the ranking content with a comparison of Hypit (official website: hypit.ai) with other AI video products.
80
80
  ```
81
81
 
82
82
  Or start without a reference video:
@@ -132,11 +132,11 @@ commands and the repository layout.
132
132
  <table>
133
133
  <tr>
134
134
  <td>Bug reports</td>
135
- <td><a href="https://github.com/hypit-ai/hypit/issues/new?labels=bug">Open an issue</a></td>
135
+ <td><a href="https://github.com/hypit-ai/hypit/issues/new?template=bug_report.yml">Open an issue</a></td>
136
136
  </tr>
137
137
  <tr>
138
138
  <td>Feature requests</td>
139
- <td><a href="https://github.com/hypit-ai/hypit/issues/new?labels=enhancement">Open an issue</a></td>
139
+ <td><a href="https://github.com/hypit-ai/hypit/issues/new?template=feature_request.yml">Open an issue</a></td>
140
140
  </tr>
141
141
  <tr>
142
142
  <td>Questions</td>
@@ -148,6 +148,12 @@ commands and the repository layout.
148
148
 
149
149
  [![Star History Chart](https://api.star-history.com/svg?repos=hypit-ai/hypit&type=Date)](https://www.star-history.com/#hypit-ai/hypit&Date)
150
150
 
151
+ ## Contributors
152
+
153
+ <a href="https://github.com/hypit-ai/hypit/graphs/contributors">
154
+ <img alt="Contributors" src="https://contrib.rocks/image?repo=hypit-ai/hypit">
155
+ </a>
156
+
151
157
  ## License
152
158
 
153
159
  Hypit is released under the [Hypit Open Source License](https://github.com/hypit-ai/hypit/blob/main/LICENSE). The videos and other outputs you create belong to you; third-party models and services may have their own terms.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypit/hypit",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "homepage": "https://hypit.ai",
5
5
  "repository": {
6
6
  "type": "git",
@@ -81,8 +81,14 @@ export function declaredExternalPackageRoot(root: string, from: string | URL, na
81
81
  while (true) {
82
82
  const manifest = join(cursor, "package.json");
83
83
  if (existsSync(manifest)) {
84
- const value = JSON.parse(readFileSync(manifest, "utf8")) as { dependencies?: Record<string, string> };
85
- const version = value.dependencies?.[name];
84
+ const value = JSON.parse(readFileSync(manifest, "utf8")) as {
85
+ dependencies?: Record<string, string>;
86
+ optionalDependencies?: Record<string, string>;
87
+ };
88
+ // An upstream asset a package ships as optional is still selected by one exact version here.
89
+ // `hypit packages install` places it under the machine npm root named by this selection, so
90
+ // reading only the required map makes the documented repair unusable for every optional asset.
91
+ const version = value.dependencies?.[name] ?? value.optionalDependencies?.[name];
86
92
  return version === undefined ? undefined : externalPackageInstallRoot(root, name, version);
87
93
  }
88
94
  const parent = dirname(cursor);
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Preloaded into the disposable capture process, which never enters `bin/hypit.mjs`.
3
+ *
4
+ * `module.registerHooks` is process-local, so the resolver the launcher installs does not
5
+ * reach a child that Node starts directly. This registers the same resolution environment
6
+ * in the child, so its view of `@hypit/*` and of the machine packages those modules declare
7
+ * is identical to its parent's.
8
+ *
9
+ * Only relative paths can be used here: no hook exists yet, and a Distribution ships its
10
+ * package sources without any `node_modules` link between them.
11
+ */
12
+ import { resolve } from "node:path";
13
+
14
+ // `bin/hypit.mjs` publishes this for every process it starts; the fallback mirrors
15
+ // `packages/video-cli/src/distribution.ts` for callers that import a Distribution directly.
16
+ const distributionRoot = resolve(
17
+ process.env.HYPIT_DISTRIBUTION_ROOT ?? resolve(import.meta.dirname, "../../.."),
18
+ );
19
+
20
+ const { installDistributionPackageResolution, installExternalPackageResolution } =
21
+ await import("../../package-loader-node/src/distribution-resolution.js");
22
+ installDistributionPackageResolution([distributionRoot]);
23
+
24
+ const { hypitHostPackageRoot } = await import("../../runtime-host-node/src/index.js");
25
+ installExternalPackageResolution([hypitHostPackageRoot()]);
@@ -25,30 +25,31 @@ async function killRenderTree(pid: number): Promise<void> {
25
25
  }
26
26
  return;
27
27
  }
28
- const { stdout } = await exec("ps", ["-A", "-o", "pid=,ppid="], { timeout: cleanupMs });
29
- const rows = stdout.trim().split("\n").map((line) => line.trim().split(/\s+/u).map(Number));
30
28
  const descendants = [pid];
31
29
  for (let i = 0; i < descendants.length; i++) {
32
- for (const [child, parent] of rows) if (parent === descendants[i] && child !== undefined) descendants.push(child);
30
+ const children = await exec("pgrep", ["-P", String(descendants[i])], { timeout: cleanupMs })
31
+ .then(({ stdout }) => stdout.trim().split(/\s+/u).filter(Boolean).map(Number),
32
+ (error) => { if ((error as { code?: unknown }).code === 1) return []; throw error; });
33
+ for (const child of children) if (!descendants.includes(child)) descendants.push(child);
33
34
  }
35
+ // Kill the whole tree before waiting: only after every ancestor is dead are
36
+ // orphaned descendants reparented and reaped, making kill(pid, 0) read ESRCH.
34
37
  for (const child of descendants.reverse()) {
35
38
  for (const target of [-child, child]) {
36
39
  try { process.kill(target, "SIGKILL"); }
37
40
  catch (error) { if ((error as NodeJS.ErrnoException).code !== "ESRCH") throw error; }
38
41
  }
39
- // Signal delivery is asynchronous. Wait for execution to stop while the
40
- // parent is still alive to reap its child; zombies no longer hold resources.
41
- const deadline = Date.now() + cleanupMs;
42
- while (true) {
43
- const state = await exec("ps", ["-p", String(child), "-o", "stat="], { timeout: cleanupMs })
44
- .then(({ stdout }) => stdout.trim(), (error) => {
45
- if (error.code === 1 && !error.stdout?.trim()) return "";
46
- throw error;
47
- });
48
- if (state === "" || state.startsWith("Z")) break;
49
- if (Date.now() >= deadline) throw new Error(`Render process ${child} did not stop after SIGKILL`);
50
- await delay(20);
51
- }
42
+ }
43
+ const deadline = Date.now() + cleanupMs;
44
+ let remaining = descendants;
45
+ while (remaining.length > 0) {
46
+ remaining = remaining.filter((child) => {
47
+ try { process.kill(child, 0); return true; }
48
+ catch (error) { if ((error as NodeJS.ErrnoException).code === "ESRCH") return false; throw error; }
49
+ });
50
+ if (remaining.length === 0) break;
51
+ if (Date.now() >= deadline) throw new Error(`Render process ${remaining[0]} did not stop after SIGKILL`);
52
+ await delay(20);
52
53
  }
53
54
  }
54
55
 
@@ -62,7 +63,13 @@ export async function runCaptureProcess(
62
63
  ): Promise<void> {
63
64
  signal.throwIfAborted();
64
65
  await new Promise<void>((resolve, reject) => {
65
- const child = spawn(process.execPath, ["--import", import.meta.resolve("tsx"), fileURLToPath(entry)], {
66
+ // The launcher's resolver hooks are process-local, so this child preloads the same
67
+ // environment before it imports any package the Distribution owns.
68
+ const child = spawn(process.execPath, [
69
+ "--import", import.meta.resolve("tsx"),
70
+ "--import", new URL("./capture-bootstrap.ts", import.meta.url).href,
71
+ fileURLToPath(entry),
72
+ ], {
66
73
  detached: process.platform !== "win32", windowsHide: true,
67
74
  stdio: ["ignore", "pipe", "pipe", "ipc"],
68
75
  });
@@ -76,7 +83,7 @@ export async function runCaptureProcess(
76
83
  const kill = () => {
77
84
  if (grace !== undefined) clearTimeout(grace);
78
85
  killing ??= (child.pid === undefined ? Promise.resolve() : killRenderTree(child.pid)).catch((error) => {
79
- failure = new Error(`${failure?.message ?? "Render cleanup failed"}; ${String(error)}`);
86
+ if (!completed) failure = new Error(`${failure?.message ?? "Render cleanup failed"}; ${String(error)}`);
80
87
  child.kill("SIGKILL");
81
88
  });
82
89
  return killing;
@@ -326,7 +326,24 @@ export const seedanceMarkupSurfaces = [
326
326
 
327
327
  /** The duration is an author literal on every Seedance Surface, so the manifest is the exact-model module's own. */
328
328
  export const seedanceManifest = seedanceBaseDefinition.manifest;
329
- export const seedanceComponent = seedanceBaseDefinition.component;
329
+
330
+ const referenceAudioProducers = new Set(Object.values(seedanceEndpoints)
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
+ };
330
347
  export const seedanceDefinition = seedanceBaseDefinition;
331
348
 
332
349
  export {
@@ -109,6 +109,9 @@ function mediaReference(
109
109
  if (value !== undefined && (value.kind !== "blob" || !value.mediaType.startsWith(`${role}/`))) {
110
110
  throw new Error(`${subject} must reference ${role} media`);
111
111
  }
112
+ if (value !== undefined && value.kind === "blob" && role === "audio" && ["audio/mp4", "audio/x-m4a"].includes(value.mediaType)) {
113
+ throw new Error(`${subject} references m4a audio, which Seedance does not accept; convert to wav or mp3 and admit that file`);
114
+ }
112
115
  return reference;
113
116
  }
114
117