@hypit/hypit 0.1.2 → 0.1.4

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.
Files changed (94) hide show
  1. package/README.md +1 -1
  2. package/dist/public/studio-adapter.d.ts +31 -2
  3. package/package.json +4 -1
  4. package/packages/audio-track-studio/src/index.ts +7 -4
  5. package/packages/build-result/README.md +58 -0
  6. package/packages/build-result/package.json +2 -1
  7. package/packages/build-result/src/decode.ts +10 -8
  8. package/packages/build-result/src/file-reference.ts +42 -0
  9. package/packages/build-result/src/index.ts +3 -0
  10. package/packages/build-result/src/replace-file-windows.ts +58 -0
  11. package/packages/build-result/src/replace-file.ts +11 -0
  12. package/packages/build-result/src/store.ts +44 -13
  13. package/packages/build-result/src/types.ts +38 -8
  14. package/packages/build-result/src/writer.ts +12 -1
  15. package/packages/build-result-fs/README.md +23 -1
  16. package/packages/build-result-s3/README.md +5 -0
  17. package/packages/build-result-s3/src/repository.ts +19 -5
  18. package/packages/caption-fine/README.md +17 -2
  19. package/packages/caption-fine/src/manifest.ts +1 -1
  20. package/packages/caption-fine/src/render.ts +48 -36
  21. package/packages/caption-fine-studio/package.json +8 -3
  22. package/packages/caption-fine-studio/src/index.ts +10 -1
  23. package/packages/cli/README.md +25 -0
  24. package/packages/cli/src/arguments.ts +16 -11
  25. package/packages/cli/src/build-planning.ts +6 -1
  26. package/packages/cli/src/command.ts +1 -1
  27. package/packages/cli/src/commands/environment.ts +42 -13
  28. package/packages/cli/src/machine-view.ts +12 -3
  29. package/packages/cli/src/main.ts +4 -0
  30. package/packages/cli/src/output.ts +28 -12
  31. package/packages/cli/src/result-export.ts +22 -13
  32. package/packages/cli/src/run-file.ts +19 -8
  33. package/packages/comment-sticker-studio/src/index.ts +2 -1
  34. package/packages/compiler-node/src/compiler.ts +1 -0
  35. package/packages/estimate/README.md +56 -3
  36. package/packages/estimate/src/program.ts +7 -8
  37. package/packages/fonts-open/README.md +5 -0
  38. package/packages/fonts-open/package.json +5 -1
  39. package/packages/fonts-open/src/studio.ts +21 -0
  40. package/packages/gpt-image-kits/README.md +17 -12
  41. package/packages/gpt-image-kits/kits/phone-ugc-v1.svs +8 -8
  42. package/packages/media-track-studio/src/index.ts +7 -2
  43. package/packages/provider-whisperx-local/README.md +57 -0
  44. package/packages/ranking/src/render.ts +11 -9
  45. package/packages/ranking-studio/README.md +11 -0
  46. package/packages/ranking-studio/package.json +8 -3
  47. package/packages/ranking-studio/src/index.ts +11 -4
  48. package/packages/runtime/src/catalog.ts +2 -0
  49. package/packages/runtime-host-node/src/index.ts +1 -0
  50. package/packages/runtime-local/README.md +19 -0
  51. package/packages/runtime-local/src/programs.ts +39 -24
  52. package/packages/runtime-local/src/runtime.ts +2 -0
  53. package/packages/script/README.md +22 -0
  54. package/packages/script/src/edit.ts +123 -73
  55. package/packages/script/src/lexical.ts +40 -6
  56. package/packages/script/src/parser.ts +36 -8
  57. package/packages/script/src/types.ts +4 -0
  58. package/packages/seedance-kits/README.md +3 -1
  59. package/packages/seedance-kits/kits/speaker-v1.svs +5 -5
  60. package/packages/speech-alignment/src/align.ts +45 -21
  61. package/packages/studio/INSPECTOR.md +104 -0
  62. package/packages/studio/README.md +65 -4
  63. package/packages/studio/src/build-library.ts +99 -84
  64. package/packages/studio/src/library-media.ts +19 -0
  65. package/packages/studio/src/parameter-values.ts +41 -1
  66. package/packages/studio/src/parameters.ts +20 -21
  67. package/packages/studio/src/server.ts +58 -30
  68. package/packages/studio/src/shared.ts +20 -6
  69. package/packages/studio/src/studio-registry.ts +22 -0
  70. package/packages/studio/src/style.css +135 -144
  71. package/packages/studio/src/temporal-edit.ts +131 -0
  72. package/packages/studio/src/ui/artifact-name.ts +80 -0
  73. package/packages/studio/src/ui/artifact-preview.ts +99 -0
  74. package/packages/studio/src/ui/icons.ts +5 -1
  75. package/packages/studio/src/ui/library.ts +373 -192
  76. package/packages/studio/src/ui/main.ts +153 -31
  77. package/packages/studio/src/ui/material-preview.ts +1 -1
  78. package/packages/studio/src/ui/overlay.ts +21 -20
  79. package/packages/studio/src/ui/sidebar-panel.ts +31 -0
  80. package/packages/studio/src/ui/stage.ts +124 -8
  81. package/packages/studio/src/ui/timeline.ts +16 -179
  82. package/packages/studio/start.ts +15 -3
  83. package/packages/studio-adapter/README.md +21 -0
  84. package/packages/studio-adapter/src/index.ts +26 -1
  85. package/packages/temporal-markup/EDITING.md +183 -0
  86. package/packages/temporal-markup/README.md +4 -0
  87. package/packages/typography-track-studio/package.json +8 -3
  88. package/packages/typography-track-studio/src/index.ts +5 -1
  89. package/packages/video-cli/README.md +12 -3
  90. package/packages/video-cli/src/creation.ts +11 -5
  91. package/packages/whisperx/README.md +7 -0
  92. package/packages/workspace/src/index.ts +2 -0
  93. package/packages/workspace-fs-node/README.md +4 -1
  94. package/packages/workspace-fs-node/src/workspace.ts +3 -1
@@ -157,7 +157,7 @@ export function parseCommand(argv: readonly string[]): CliCommand {
157
157
  }
158
158
  case "status": {
159
159
  const [build, rest] = requiredPositional(tail, "status requires one Build id");
160
- const options = commandOptions(command, rest, "--runtime", "--watch", "--max-wait-ms", "--limit");
160
+ const options = commandOptions(command, rest, "--runtime", "--workspace", "--watch", "--max-wait-ms", "--limit");
161
161
  if (!options.watch && options.maxWaitMs !== undefined) {
162
162
  throw new Error("--max-wait-ms applies to status --watch");
163
163
  }
@@ -172,7 +172,7 @@ export function parseCommand(argv: readonly string[]): CliCommand {
172
172
  };
173
173
  }
174
174
  case "activity": {
175
- const options = commandOptions(command, tail, "--runtime", "--watch", "--jsonl", "--limit");
175
+ const options = commandOptions(command, tail, "--runtime", "--workspace", "--watch", "--jsonl", "--limit");
176
176
  if (options.presentation.jsonl === true && !options.watch) {
177
177
  throw new Error("--jsonl applies only to activity --watch");
178
178
  }
@@ -189,7 +189,7 @@ export function parseCommand(argv: readonly string[]): CliCommand {
189
189
  }
190
190
  case "cancel": {
191
191
  const [build, rest] = requiredPositional(tail, "cancel requires one Build id");
192
- const options = commandOptions(command, rest, "--runtime", "--reason");
192
+ const options = commandOptions(command, rest, "--runtime", "--workspace", "--reason");
193
193
  return {
194
194
  command,
195
195
  build,
@@ -205,17 +205,20 @@ export function parseCommand(argv: readonly string[]): CliCommand {
205
205
  case "auth": return parseAuthCommand(tail);
206
206
  case "doctor": {
207
207
  const [profile, rest] = optionalPositional(tail);
208
- const options = commandOptions(command, rest, "--workspace", "--limit");
208
+ const options = commandOptions(command, rest, "--workspace", "--runtime", "--limit");
209
+ if (profile !== undefined && options.runtimeProfile !== undefined) {
210
+ throw new Error("doctor accepts the Runtime Profile either positionally or with --runtime, not both");
211
+ }
209
212
  return {
210
213
  command,
211
214
  presentation: options.presentation,
212
215
  limit: options.limit,
213
216
  ...optionalProject(options),
214
- ...runtimeOption(profile),
217
+ ...runtimeOption(profile ?? options.runtimeProfile),
215
218
  };
216
219
  }
217
220
  case "paths": {
218
- const options = commandOptions(command, tail, "--runtime");
221
+ const options = commandOptions(command, tail, "--runtime", "--workspace");
219
222
  return { command, presentation: options.presentation, ...optionalRuntime(options) };
220
223
  }
221
224
  case "_worker": {
@@ -244,7 +247,7 @@ function parseResultCommand(tail: readonly string[]): ProjectResultCommand | Exe
244
247
  }
245
248
  const [build, rest] = requiredPositional(values, `result ${action} requires one Build id`);
246
249
  if (action === "finish" || action === "discard") {
247
- const options = commandOptions(`result ${action}`, rest, "--runtime");
250
+ const options = commandOptions(`result ${action}`, rest, "--runtime", "--workspace");
248
251
  return { command: "result", action, build, presentation: options.presentation, ...optionalRuntime(options) };
249
252
  }
250
253
  const options = commandOptions("result edit", rest,
@@ -306,7 +309,7 @@ function parseRuntimeCommand(tail: readonly string[]): RuntimeSelectionCommand |
306
309
  : action === "status"
307
310
  ? ["--runtime", "--limit"] as const
308
311
  : ["--runtime", "--lines"] as const;
309
- const options = commandOptions(`runtime ${action}`, rest, ...allowed);
312
+ const options = commandOptions(`runtime ${action}`, rest, "--workspace", ...allowed);
310
313
  rejectDuplicateProfile("runtime", profile, options.runtimeProfile);
311
314
  const runtime = runtimeOption(profile ?? options.runtimeProfile);
312
315
  const common = {
@@ -314,6 +317,7 @@ function parseRuntimeCommand(tail: readonly string[]): RuntimeSelectionCommand |
314
317
  action,
315
318
  presentation: options.presentation,
316
319
  ...runtime,
320
+ ...optionalProject(options),
317
321
  };
318
322
  if (action === "up" || action === "down") {
319
323
  return { ...common, action, ...(options.maxWaitMs === undefined ? {} : { maxWaitMs: options.maxWaitMs }) };
@@ -329,7 +333,7 @@ function parseProgramsCommand(tail: readonly string[]): ProgramsCommand {
329
333
  }
330
334
  const [profile, rest] = optionalPositional(values);
331
335
  const options = commandOptions(`programs ${action}`, rest,
332
- "--runtime", "--limit", "--max-wait-ms");
336
+ "--runtime", "--workspace", "--limit", "--max-wait-ms");
333
337
  if (action !== "up" && options.maxWaitMs !== undefined) {
334
338
  throw new Error("--max-wait-ms applies to programs up");
335
339
  }
@@ -341,6 +345,7 @@ function parseProgramsCommand(tail: readonly string[]): ProgramsCommand {
341
345
  presentation: options.presentation,
342
346
  limit: options.limit,
343
347
  ...runtime,
348
+ ...optionalProject(options),
344
349
  };
345
350
  return action === "up"
346
351
  ? { ...common, action, ...(options.maxWaitMs === undefined ? {} : { maxWaitMs: options.maxWaitMs }) }
@@ -362,7 +367,7 @@ function parseAuthCommand(tail: readonly string[]): AuthCommand {
362
367
  }
363
368
  const [endpoint, rest] = requiredPositional(values, `auth ${action} requires one Endpoint instance`);
364
369
  const options = commandOptions(`auth ${action}`, rest,
365
- "--runtime", "--slot", ...(action === "login" ? ["--from"] as const : []),
370
+ "--runtime", "--workspace", "--slot", ...(action === "login" ? ["--from"] as const : []),
366
371
  ...(action === "status" ? ["--limit"] as const : []));
367
372
  const common = {
368
373
  command: "auth" as const,
@@ -576,7 +581,7 @@ function optionalProject(options: RawOptions): ProjectOption {
576
581
  }
577
582
 
578
583
  function optionalRuntime(options: RawOptions): RuntimeOption {
579
- return runtimeOption(options.runtimeProfile);
584
+ return { ...runtimeOption(options.runtimeProfile), ...optionalProject(options) };
580
585
  }
581
586
 
582
587
  function runtimeOption(profile: string | undefined): RuntimeOption {
@@ -24,7 +24,12 @@ export function createCatalogDescriptor(options: {
24
24
  if (item.ref.kind === "operation-result") {
25
25
  throw new Error(`public output ${item.name} was not lowered to a stable Record or Logical Output`);
26
26
  }
27
- return item.ref.kind === "logical-output" ? [{ name: item.name, ref: item.ref }] : [];
27
+ if (item.ref.kind !== "logical-output") return [];
28
+ const outputId = item.ref.id;
29
+ const element = options.compilation.provenance.elements.find((element) => element.outputs.some((output) => output.id === outputId));
30
+ const authored = element?.outputs.find((output) => output.id === outputId);
31
+ const displayName = element?.components.find((component) => component.id === authored?.component)?.local;
32
+ return [{ name: item.name, ref: item.ref, ...(displayName === undefined ? {} : { displayName }) }];
28
33
  });
29
34
  const names = new Set<string>();
30
35
  const outputs = new Set<string>();
@@ -4,7 +4,7 @@ type CommandBase = {
4
4
  readonly presentation: CliOutputOptions;
5
5
  };
6
6
 
7
- export type RuntimeOption = {
7
+ export type RuntimeOption = ProjectOption & {
8
8
  readonly runtimeProfile: string | undefined;
9
9
  };
10
10
 
@@ -10,9 +10,27 @@ import { acquireOAuthCredential } from "../oauth.js";
10
10
  import { writeCliOutput } from "../output.js";
11
11
  import type { CliIo } from "../output.js";
12
12
  import { hypitHostStateRoot, hypitProjectStateRoot } from "../paths.js";
13
- import type { CliRuntimeController } from "../runtime-port.js";
13
+ import type { CliManagedProgramReport, CliRuntimeController } from "../runtime-port.js";
14
14
  import type { OperationalWriter } from "./types.js";
15
15
 
16
+ function programRecord(item: CliManagedProgramReport) {
17
+ return {
18
+ ...item,
19
+ state: item.state.state,
20
+ ...(item.state.state === "ready" ? {} : { stateDetail: item.state.detail }),
21
+ };
22
+ }
23
+
24
+ function programDescription(item: CliManagedProgramReport): string {
25
+ const details = [...new Set([
26
+ ...(item.state.state === "ready" ? [] : [item.state.detail]),
27
+ ...(item.detail === undefined ? [] : [item.detail]),
28
+ ])];
29
+ return `${item.id}: ${item.state.state}${details.length === 0 ? "" : ` — ${details.join("; ")}`}`
30
+ + (item.pid === undefined ? "" : ` · PID ${item.pid}`)
31
+ + (item.logPath === undefined ? "" : ` · log ${item.logPath}`);
32
+ }
33
+
16
34
  export function isEnvironmentCommand(args: CliCommand): args is EnvironmentCommand {
17
35
  return args.command === "paths" || args.command === "packages" || args.command === "doctor"
18
36
  || args.command === "programs" || args.command === "auth"
@@ -23,6 +41,7 @@ export function isEnvironmentCommand(args: CliCommand): args is EnvironmentComma
23
41
  export async function runEnvironmentCommand(input: {
24
42
  readonly args: EnvironmentCommand;
25
43
  readonly runtimeProfile: string | undefined;
44
+ readonly runtimeSelectionFile: string | undefined;
26
45
  readonly io: CliIo;
27
46
  readonly distribution: CliDistribution;
28
47
  readonly projectRoot: string;
@@ -32,11 +51,16 @@ export async function runEnvironmentCommand(input: {
32
51
  readonly write: OperationalWriter;
33
52
  }): Promise<void> {
34
53
  const {
35
- args, runtimeProfile, io, distribution, projectRoot, packageRootForProject, runtimeHost, runtimeController, write,
54
+ args, runtimeProfile, runtimeSelectionFile, io, distribution, projectRoot, packageRootForProject, runtimeHost, runtimeController, write,
36
55
  } = input;
56
+ const profileSource: "none" | "argument" | "project" = runtimeProfile === undefined
57
+ ? "none" : runtimeSelectionFile === undefined ? "argument" : "project";
58
+ const selectionDescription = profileSource === "argument"
59
+ ? "command argument (this invocation only)"
60
+ : runtimeSelectionFile ?? "none";
37
61
  const reportProgramProgress = args.presentation.json
38
62
  ? undefined
39
- : (event: { readonly id: string; readonly phase: "checking" | "installing" | "starting" | "waiting" | "ready" }): void => {
63
+ : (event: { readonly id: string; readonly phase: "checking" | "installing" | "starting" | "waiting" | "ready"; readonly logPath?: string }): void => {
40
64
  if (!args.presentation.verbose && event.phase !== "installing" && event.phase !== "starting") return;
41
65
  const verb = {
42
66
  checking: "Checking",
@@ -45,7 +69,7 @@ export async function runEnvironmentCommand(input: {
45
69
  waiting: "Waiting for",
46
70
  ready: "Ready",
47
71
  }[event.phase];
48
- io.write(` · ${verb} ${event.id}\n`);
72
+ io.write(` · ${verb} ${event.id}${event.logPath === undefined ? "" : ` · log ${event.logPath}`}\n`);
49
73
  };
50
74
  const reportPackageProgress = args.presentation.json
51
75
  ? undefined
@@ -63,6 +87,8 @@ export async function runEnvironmentCommand(input: {
63
87
  const machine = {
64
88
  format: "hypit.cli-paths@1" as const,
65
89
  project: projectRoot,
90
+ profileSource,
91
+ selectionFile: resolve(projectRoot, ".hypit/runtime"),
66
92
  projectState: hypitProjectStateRoot(projectRoot),
67
93
  ...(runtimeProfile === undefined ? {} : { profile: runtimeProfile }),
68
94
  ...(runtimePaths === undefined ? {} : { runtimeData: runtimePaths.runtimeDataRoot }),
@@ -74,11 +100,14 @@ export async function runEnvironmentCommand(input: {
74
100
  ["Project", machine.project],
75
101
  ["Project state", machine.projectState],
76
102
  ["Runtime Profile", machine.profile ?? "not selected"],
103
+ ["Runtime selection", selectionDescription],
77
104
  ["Runtime data", machine.runtimeData ?? "not selected"],
78
105
  ["Host state", machine.hostState],
79
106
  ["Machine packages", machine.machinePackages],
80
107
  ["Distribution", machine.distribution ?? "embedded"],
81
- ]);
108
+ ], runtimeProfile === undefined
109
+ ? ["Select an existing Profile with hypit runtime use <profile> --workspace <project>, or create one with hypit runtime init."]
110
+ : []);
82
111
  return;
83
112
  }
84
113
 
@@ -122,6 +151,8 @@ export async function runEnvironmentCommand(input: {
122
151
  format: "hypit.cli-doctor@1" as const,
123
152
  ok: !diagnostics.some((item) => item.severity === "error"),
124
153
  project: projectRoot,
154
+ profileSource,
155
+ ...(runtimeSelectionFile === undefined ? {} : { selectionFile: runtimeSelectionFile }),
125
156
  ...(profile === undefined ? {} : { profile }),
126
157
  diagnosticCount: diagnostics.length,
127
158
  diagnostics: diagnostics.slice(0, args.limit),
@@ -151,7 +182,7 @@ export async function runEnvironmentCommand(input: {
151
182
  const ready = result.programs.every((item) => item.state.state === "ready");
152
183
  const desiredState = args.action === "down" ? !result.programs.some((item) => item.state.state === "ready") : ready;
153
184
  const lifecycleOk = args.action === "status" || desiredState;
154
- const shownPrograms = result.programs.filter((item) => item.state.state !== "ready").slice(0, args.limit);
185
+ const shownPrograms = result.programs.filter((item) => args.presentation.verbose || item.state.state !== "ready").slice(0, args.limit);
155
186
  const title = args.action === "up"
156
187
  ? desiredState ? "External programs ready" : "External programs need attention"
157
188
  : args.action === "down"
@@ -161,11 +192,7 @@ export async function runEnvironmentCommand(input: {
161
192
  format: "hypit.cli-programs@1",
162
193
  action: args.action,
163
194
  ready,
164
- programs: result.programs.slice(0, args.limit).map((item) => ({
165
- id: item.id,
166
- state: item.state.state,
167
- ...(args.presentation.verbose && item.action !== undefined ? { action: item.action } : {}),
168
- })),
195
+ programs: result.programs.slice(0, args.limit).map(programRecord),
169
196
  ...(result.programs.length <= args.limit ? {} : { omittedPrograms: result.programs.length - args.limit }),
170
197
  }, title,
171
198
  args.action === "status" ? ready ? "success" : "info" : lifecycleOk ? "success" : "warning", [
@@ -173,7 +200,7 @@ export async function runEnvironmentCommand(input: {
173
200
  ["Programs", String(result.programs.length)] as const,
174
201
  ["Ready", String(result.programs.filter((item) => item.state.state === "ready").length)] as const,
175
202
  ]),
176
- ], shownPrograms.map((item) => `${item.id}: ${item.state.state}`));
203
+ ], shownPrograms.map(programDescription));
177
204
  if (!lifecycleOk) io.setExitCode?.(1);
178
205
  return;
179
206
  }
@@ -207,6 +234,7 @@ export async function runEnvironmentCommand(input: {
207
234
  programs: {
208
235
  total: external.programs.length,
209
236
  ready: external.programs.filter((item) => item.state.state === "ready").length,
237
+ items: external.programs.map(programRecord),
210
238
  },
211
239
  }, ok ? "Local Runtime ready" : "Local Runtime needs attention", ok ? "success" : "warning", [
212
240
  ...(!args.presentation.verbose && ok ? [] : [
@@ -214,7 +242,8 @@ export async function runEnvironmentCommand(input: {
214
242
  ["Worker", processState.state] as const,
215
243
  ["Managed programs", `${external.programs.filter((item) => item.state.state === "ready").length}/${external.programs.length} ready`] as const,
216
244
  ]),
217
- ]);
245
+ ], external.programs.filter((item) => args.presentation.verbose || item.state.state !== "ready")
246
+ .map(programDescription));
218
247
  if (!ok) io.setExitCode?.(1);
219
248
  return;
220
249
  }
@@ -1,15 +1,24 @@
1
1
  import type { CliBuildResultView, CliBuildStatusView, CliOutputView } from "./view.js";
2
2
 
3
- type ProgramStateView = { readonly id: string; readonly state: string; readonly action?: string };
3
+ type ProgramStateView = {
4
+ readonly id: string;
5
+ readonly state: string;
6
+ readonly action?: string;
7
+ readonly endpoint?: string;
8
+ readonly stateDetail?: string;
9
+ readonly detail?: string;
10
+ readonly pid?: number;
11
+ readonly logPath?: string;
12
+ };
4
13
  type AttentionView = { readonly message: string; readonly action?: string };
5
14
 
6
15
  export type OperationalMachineView =
7
16
  | { readonly format: "hypit.cli-runtime-init@1"; readonly profile: string; readonly project: string; readonly selected: true }
8
17
  | { readonly format: "hypit.cli-runtime-selection@1"; readonly selected: boolean; readonly removed?: boolean; readonly profile?: string; readonly project?: string }
9
- | { readonly format: "hypit.cli-paths@1"; readonly project: string; readonly projectState: string; readonly profile?: string; readonly runtimeData?: string; readonly hostState: string; readonly machinePackages: string; readonly distribution?: string }
18
+ | { readonly format: "hypit.cli-paths@1"; readonly project: string; readonly projectState: string; readonly profileSource: "argument" | "project" | "none"; readonly selectionFile: string; readonly profile?: string; readonly runtimeData?: string; readonly hostState: string; readonly machinePackages: string; readonly distribution?: string }
10
19
  | { readonly format: "hypit.cli-package@1"; readonly action: "install" | "status"; readonly package: string; readonly ready: boolean }
11
20
  | { readonly format: "hypit.cli-programs@1"; readonly action: "up" | "down" | "status"; readonly ready: boolean; readonly programs: readonly ProgramStateView[]; readonly omittedPrograms?: number }
12
- | { readonly format: "hypit.cli-runtime-up@1"; readonly ready: boolean; readonly worker: string; readonly preparedPackages: number; readonly programs: { readonly total: number; readonly ready: number } }
21
+ | { readonly format: "hypit.cli-runtime-up@1"; readonly ready: boolean; readonly worker: string; readonly preparedPackages: number; readonly programs: { readonly total: number; readonly ready: number; readonly items: readonly ProgramStateView[] } }
13
22
  | { readonly format: "hypit.cli-runtime-logs@1"; readonly lines: readonly string[]; readonly totalLines: number; readonly omittedLines: number; readonly path?: string }
14
23
  | { readonly format: "hypit.cli-runtime-down@1"; readonly worker: string }
15
24
  | { readonly format: "hypit.cli-runtime-status@1"; readonly ready: boolean; readonly attention: boolean; readonly worker: { readonly state: string; readonly configuration?: string }; readonly builds: Readonly<Record<string, number>>; readonly programs: { readonly total: number; readonly ready: number; readonly unavailable: readonly ProgramStateView[] }; readonly capacity: { readonly active: number } }
@@ -177,12 +177,14 @@ export async function runCli(
177
177
  }
178
178
 
179
179
  let runtimeProfile = acceptsRuntimeContext(args) ? args.runtimeProfile : undefined;
180
+ let runtimeSelectionFile: string | undefined;
180
181
  const runtimeWasExplicit = runtimeProfile !== undefined;
181
182
  let runtimeNeedsHint = runtimeWasExplicit;
182
183
  if (acceptsRuntimeContext(args) && runtimeProfile === undefined) {
183
184
  const selected = await findRuntimeProfile(await commandProjectRoot());
184
185
  if (selected !== undefined) {
185
186
  runtimeProfile = selected.profile;
187
+ runtimeSelectionFile = selected.selectionFile;
186
188
  runtimeNeedsHint = false;
187
189
  }
188
190
  }
@@ -196,6 +198,7 @@ export async function runCli(
196
198
  await runEnvironmentCommand({
197
199
  args,
198
200
  runtimeProfile,
201
+ runtimeSelectionFile,
199
202
  io,
200
203
  distribution,
201
204
  projectRoot: await commandProjectRoot(),
@@ -387,6 +390,7 @@ export async function runCli(
387
390
  result: {
388
391
  repository: buildResults.location,
389
392
  ...(args.title === undefined ? {} : { title: args.title }),
393
+ resourceReferences: loadedRun.resultResourceReferences,
390
394
  forwards: result.resultForwards.filter((forward) =>
391
395
  catalog.publishedOutputs.some((published) => published.ref.id === forward.output)),
392
396
  },
@@ -43,6 +43,8 @@ export type DoctorOutput = {
43
43
  readonly ok: boolean;
44
44
  readonly project: string;
45
45
  readonly profile?: string;
46
+ readonly profileSource?: "argument" | "project" | "none";
47
+ readonly selectionFile?: string;
46
48
  readonly diagnosticCount: number;
47
49
  readonly diagnostics: readonly CliDiagnostic[];
48
50
  readonly omittedDiagnostics?: number;
@@ -272,9 +274,16 @@ function renderDoctor(view: Extract<CliPresentation, { kind: "doctor" }>, io: Cl
272
274
  const lines = [colors.accent(colors.strong("Hypit Doctor")), ""];
273
275
  lines.push(...facts([
274
276
  ["Project", shortPath(view.machine.project)],
275
- ...(view.machine.profile === undefined ? [] : [["Profile", shortPath(view.machine.profile)] as const]),
277
+ ["Runtime Profile", view.machine.profile === undefined ? "not selected" : shortPath(view.machine.profile)],
278
+ ...(view.machine.profileSource === "argument"
279
+ ? [["Runtime selection", "command argument (this invocation only)"] as const]
280
+ : view.machine.selectionFile === undefined ? [] : [["Runtime selection", shortPath(view.machine.selectionFile)] as const]),
281
+ ["Scope", view.machine.profile === undefined ? "project Results only" : "selected Runtime and project Results"],
276
282
  ], colors));
277
283
  lines.push("");
284
+ if (view.machine.profile === undefined) {
285
+ lines.push("Select an existing Profile with hypit runtime use <profile>, or pass --runtime <profile> to check it.", "");
286
+ }
278
287
  if (view.machine.diagnostics.length === 0) {
279
288
  lines.push(heading("success", "No problems found", io, colors));
280
289
  } else {
@@ -770,7 +779,8 @@ function commandHelp(topic: string, colors: Palette): readonly string[] | undefi
770
779
  colors.accent(colors.strong("hypit doctor")),
771
780
  colors.dim("Diagnose project Results and, when selected or supplied, one Runtime Profile."),
772
781
  "",
773
- " hypit doctor [<runtime-profile>] [--workspace <project>]",
782
+ " hypit doctor [--runtime <profile>] [--workspace <project>]",
783
+ " A positional Profile is also accepted in place of --runtime.",
774
784
  " Without a Runtime Profile, checks only the project's selected Result Store.",
775
785
  ],
776
786
  plan: [
@@ -819,6 +829,7 @@ function commandHelp(topic: string, colors: Palette): readonly string[] | undefi
819
829
  " hypit runtime down [<profile>] stop the Worker; external programs keep running",
820
830
  "",
821
831
  "The project is resolved first. Selection is read only from that project's .hypit/runtime.",
832
+ "All runtime actions accept --workspace <project>; operational actions also accept --runtime <profile>.",
822
833
  "No Profile filename discovery or parent-project inheritance is performed.",
823
834
  "Remote Endpoints such as HypiHub are not started by this command; use doctor to test them.",
824
835
  ],
@@ -836,22 +847,26 @@ function commandHelp(topic: string, colors: Palette): readonly string[] | undefi
836
847
  colors.accent(colors.strong("hypit programs")),
837
848
  colors.dim("Prepare and operate external programs declared by Endpoints in one Runtime Profile."),
838
849
  "",
839
- " hypit programs up [<profile>] [--max-wait-ms <ms>]",
840
- " hypit programs status [<profile>]",
841
- " hypit programs down [<profile>]",
850
+ " hypit programs up [--runtime <profile>] [--workspace <project>] [--max-wait-ms <ms>]",
851
+ " hypit programs status [--runtime <profile>] [--workspace <project>]",
852
+ " hypit programs down [--runtime <profile>] [--workspace <project>]",
853
+ " A positional Profile is also accepted in place of --runtime.",
842
854
  ],
843
855
  activity: [
844
856
  colors.accent(colors.strong("hypit activity")),
845
857
  colors.dim("Inspect active Builds and Provider pool capacity."),
846
858
  "",
847
- " hypit activity [--runtime <profile>] [--watch]",
848
- " hypit activity [--runtime <profile>] --watch --jsonl",
859
+ " hypit activity [--workspace <project>] [--runtime <profile>] [--watch]",
860
+ " hypit activity [--workspace <project>] [--runtime <profile>] --watch --jsonl",
849
861
  ],
850
862
  paths: [
851
863
  colors.accent(colors.strong("hypit paths")),
852
864
  colors.dim("Show project, Runtime and host state locations without creating them."),
853
865
  "",
854
- " hypit paths [--runtime <profile>]",
866
+ " hypit paths [--workspace <project>] [--runtime <profile>]",
867
+ " Shows whether Runtime selection comes from a command argument or the project's .hypit/runtime.",
868
+ " Without --workspace, the nearest package.json above cwd defines the project (otherwise cwd).",
869
+ " Relative command-line paths start at cwd, including when --workspace is supplied.",
855
870
  ],
856
871
  builds: [
857
872
  colors.accent(colors.strong("hypit builds")),
@@ -863,7 +878,7 @@ function commandHelp(topic: string, colors: Palette): readonly string[] | undefi
863
878
  colors.accent(colors.strong("hypit status")),
864
879
  colors.dim("Show one Build now, or keep watching it without owning execution."),
865
880
  "",
866
- " hypit status <build-id> [--runtime <profile>] [--watch]",
881
+ " hypit status <build-id> [--workspace <project>] [--runtime <profile>] [--watch]",
867
882
  " --watch observe until a Result outcome or operator attention",
868
883
  " --max-wait-ms <ms> stop watching after a bounded wait",
869
884
  ],
@@ -892,14 +907,14 @@ function commandHelp(topic: string, colors: Palette): readonly string[] | undefi
892
907
  colors.accent(colors.strong("hypit cancel")),
893
908
  colors.dim("Stop one Build and request cancellation of submitted work when supported."),
894
909
  "",
895
- " hypit cancel <build-id> [--runtime <profile>] [--reason <text>]",
910
+ " hypit cancel <build-id> [--workspace <project>] [--runtime <profile>] [--reason <text>]",
896
911
  ],
897
912
  result: [
898
913
  colors.accent(colors.strong("hypit result")),
899
914
  colors.dim("Edit one Result, finish an interrupted Result write, or discard an incomplete submission."),
900
915
  "",
901
- " hypit result finish <build-id> [--runtime <profile>]",
902
- " hypit result discard <build-id> [--runtime <profile>]",
916
+ " hypit result finish <build-id> [--workspace <project>] [--runtime <profile>]",
917
+ " hypit result discard <build-id> [--workspace <project>] [--runtime <profile>]",
903
918
  " hypit result edit <build-id> [--workspace <project>] [--title <text>] [--note <text>]",
904
919
  " [--highlight <output> ...]",
905
920
  " --clear-title remove the Result's human title",
@@ -913,6 +928,7 @@ function commandHelp(topic: string, colors: Palette): readonly string[] | undefi
913
928
  " hypit auth status <endpoint-instance> [--runtime <profile>] [--slot <name>]",
914
929
  " hypit auth login <endpoint-instance> [--runtime <profile>] [--slot <name>] [--from <secret-file>]",
915
930
  " hypit auth logout <endpoint-instance> [--runtime <profile>] [--slot <name>]",
931
+ " All auth actions accept --workspace <project> to use that project's selection.",
916
932
  ],
917
933
  };
918
934
  const selected = topics[topic];
@@ -1,3 +1,4 @@
1
+ import { fileReferenceIdentity } from "@hypit/build-result";
1
2
  import { randomUUID } from "node:crypto";
2
3
  import { lstat, mkdir, rename, rm, writeFile } from "node:fs/promises";
3
4
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
@@ -53,7 +54,7 @@ async function openResultFile(
53
54
  file: BuildResultFileRef,
54
55
  ): Promise<AsyncIterable<Uint8Array>> {
55
56
  const input = await repository.openFile(build, file);
56
- if (input === undefined) throw new Error(`Build ${build} file ${file.path} is unavailable`);
57
+ if (input === undefined) throw new Error(`File ${fileReferenceIdentity(build, file)} is unavailable`);
57
58
  return input;
58
59
  }
59
60
 
@@ -83,20 +84,28 @@ async function exportComposite(
83
84
  const temporary = join(dirname(target), `.${basename(target)}.part-${randomUUID()}`);
84
85
  try {
85
86
  await mkdir(temporary);
86
- const copied = new Set<string>();
87
+ const copied = new Map<string, BuildResultFileRef>();
88
+ const paths = new Set<string>();
89
+ const bindings = [];
87
90
  for (const binding of resolvedOutput.value.document.resources) {
88
- const file = binding.file;
89
- if (copied.has(file.path)) continue;
90
- copied.add(file.path);
91
- assert(file.path !== "value.json", "Composite Result file value.json conflicts with its exported value document");
92
- await writeStream(
93
- containedPath(temporary, file.path),
94
- await openResultFile(repository, resolvedOutput.build, file),
95
- );
91
+ const file = await repository.describeFile(resolvedOutput.build, binding.file);
92
+ const identity = fileReferenceIdentity(resolvedOutput.build, file);
93
+ let local = copied.get(identity);
94
+ if (local === undefined) {
95
+ let path = file.kind === "build-file" ? file.path : `files/file-${copied.size + 1}`;
96
+ const name = basename(path);
97
+ let index = copied.size + 1;
98
+ while (paths.has(path) || path === "value.json") path = `files/import-${index++}/${name}`;
99
+ paths.add(path);
100
+ local = { kind: "build-file", path, size: file.size, mediaType: file.mediaType };
101
+ copied.set(identity, local);
102
+ await writeStream(containedPath(temporary, path), await openResultFile(repository, resolvedOutput.build, file));
103
+ }
104
+ bindings.push({ at: binding.at, file: local });
96
105
  }
97
106
  await writeFile(
98
107
  join(temporary, "value.json"),
99
- `${JSON.stringify(resolvedOutput.value.document, null, 2)}\n`,
108
+ `${JSON.stringify({ ...resolvedOutput.value.document, resources: bindings }, null, 2)}\n`,
100
109
  { flag: "wx" },
101
110
  );
102
111
  await rename(temporary, target);
@@ -122,7 +131,7 @@ export async function exportBuildResultOutput(
122
131
  const target = resolve(destination);
123
132
  if (resolvedOutput.value.kind === "value") {
124
133
  await exportComposite(repository, { ...resolvedOutput, value: resolvedOutput.value }, target);
125
- } else if (resolvedOutput.value.kind === "build-file") {
134
+ } else if ((resolvedOutput.value.kind === "build-file" || resolvedOutput.value.kind === "external-file")) {
126
135
  const resource = resolvedOutput.value;
127
136
  await exportFile(target, async (temporary) => {
128
137
  await writeStream(
@@ -142,7 +151,7 @@ export async function exportBuildResultOutput(
142
151
  type: publishedOutput.type,
143
152
  kind: resolvedOutput.value.kind === "inline"
144
153
  ? "scalar"
145
- : resolvedOutput.value.kind === "build-file" ? "resource" : "composite",
154
+ : (resolvedOutput.value.kind === "build-file" || resolvedOutput.value.kind === "external-file") ? "resource" : "composite",
146
155
  path: target,
147
156
  };
148
157
  }
@@ -1,3 +1,4 @@
1
+ import { fileReferenceIdentity, ownedFileReference } from "@hypit/build-result";
1
2
  import { randomUUID } from "node:crypto";
2
3
  import type {
3
4
  BuildResultRepository,
@@ -24,15 +25,17 @@ import type { RunFrontend } from "@hypit/run";
24
25
  export type LoadedRunFile = NodeCompiledRun & {
25
26
  readonly path: string;
26
27
  readonly compiler: NodeRunCompiler;
28
+ readonly resultResourceReferences: Readonly<Record<string, BuildResultFileRef>>;
27
29
  };
28
30
 
29
31
  type BuildResultResolutionSession = {
30
32
  readonly files: Map<string, ArtifactAttachment>;
33
+ readonly references: Map<string, BuildResultFileRef>;
31
34
  readonly outputs: Map<string, Promise<RepositoryBuildResultOutput | undefined>>;
32
35
  };
33
36
 
34
37
  function createBuildResultResolutionSession(): BuildResultResolutionSession {
35
- return { files: new Map(), outputs: new Map() };
38
+ return { files: new Map(), outputs: new Map(), references: new Map() };
36
39
  }
37
40
 
38
41
  export async function checkRunFile(options: {
@@ -88,9 +91,10 @@ export async function resolveBuildResultValue(
88
91
  const manifest = await repository.read(build);
89
92
  if (manifest?.outcome === undefined) return undefined;
90
93
  const resultAttachment = async (owner: string, file: BuildResultFileRef): Promise<ArtifactAttachment> => {
91
- const address = `${owner}\u0000${file.path}`;
94
+ const address = fileReferenceIdentity(owner, file);
92
95
  const existing = session.files.get(address);
93
96
  if (existing !== undefined) return existing;
97
+ file = await repository.describeFile(owner, file);
94
98
  const artifact: BlobRef = {
95
99
  kind: "blob",
96
100
  resource: `res_${randomUUID()}`,
@@ -101,11 +105,12 @@ export async function resolveBuildResultValue(
101
105
  artifact,
102
106
  async open() {
103
107
  const stream = await repository.openFile(owner, file);
104
- if (stream === undefined) throw new Error(`Build ${owner} file ${file.path} is unavailable`);
108
+ if (stream === undefined) throw new Error(`File ${address} is unavailable`);
105
109
  return stream;
106
110
  },
107
111
  };
108
112
  session.files.set(address, attachment);
113
+ session.references.set(artifact.resource, ownedFileReference(owner, file));
109
114
  return attachment;
110
115
  };
111
116
  const attachments = new Map<string, ArtifactAttachment>();
@@ -120,7 +125,7 @@ export async function resolveBuildResultValue(
120
125
  const owner = resolved.build === build ? manifest : await repository.read(resolved.build);
121
126
  if (owner?.outcome === undefined) return undefined;
122
127
  let value: StoredValue;
123
- if (resolved.value.kind === "build-file") {
128
+ if (resolved.value.kind === "build-file" || resolved.value.kind === "external-file") {
124
129
  const attachment = await resultAttachment(resolved.build, resolved.value);
125
130
  attachments.set(attachment.artifact.resource, attachment);
126
131
  value = attachment.artifact;
@@ -147,8 +152,7 @@ function createRunCompiler(options: {
147
152
  readonly frontends: readonly RunFrontend[];
148
153
  readonly packageContributions: readonly NodePackageContribution[];
149
154
  readonly results?: BuildResultRepository;
150
- }): NodeRunCompiler {
151
- const resultSession = createBuildResultResolutionSession();
155
+ }, resultSession = createBuildResultResolutionSession()): NodeRunCompiler {
152
156
  const fragments = new RunFragmentRegistry();
153
157
  for (const item of options.packageContributions) {
154
158
  installRunFragmentHostFacets(item.hostFacets ?? [], fragments);
@@ -180,7 +184,14 @@ export async function loadRunFile(options: {
180
184
  readonly packageContributions: readonly NodePackageContribution[];
181
185
  readonly results?: BuildResultRepository;
182
186
  }): Promise<LoadedRunFile> {
183
- const compiler = createRunCompiler(options);
187
+ const resultSession = createBuildResultResolutionSession();
188
+ const compiler = createRunCompiler(options, resultSession);
184
189
  const compiled = await compiler.compileSource(options.workspace.entry, options.workspace);
185
- return { path: options.workspace.entry.id, compiler, ...compiled };
190
+ const references = new Map<string, BuildResultFileRef>(compiled.attachments.flatMap((attachment) =>
191
+ attachment.location === undefined ? [] : [[attachment.artifact.resource, {
192
+ kind: "external-file" as const, uri: attachment.location,
193
+ size: attachment.artifact.size, mediaType: attachment.artifact.mediaType,
194
+ }]]));
195
+ for (const [resource, file] of resultSession.references) references.set(resource, file);
196
+ return { path: options.workspace.entry.id, compiler, ...compiled, resultResourceReferences: Object.fromEntries(references) };
186
197
  }
@@ -117,12 +117,13 @@ export const commentStickerStudioTrackCompanions: readonly StudioTrackCompanion[
117
117
  inspector: [
118
118
  ...frameParameters.filter(({ writable }) => writable === true).map(({ name }) => ({
119
119
  binding: `frame.${name}`, label: title(name), domain: "where" as const,
120
- page: { id: "frame", label: "Frame" }, section: { id: "frame", label: "Frame" }, control: "text" as const,
120
+ page: { id: "frame", label: "Frame" }, section: { id: "frame", label: "Frame" }, control: "number" as const, number: { suffixes: ["%", "px"], step: 1 },
121
121
  })),
122
122
  ...commentInspector,
123
123
  ...["comment", "author", "header", "meta"].map((binding) => ({
124
124
  binding, label: title(binding), domain: "how" as const,
125
125
  page: { id: "copy", label: "Copy" }, section: { id: "copy", label: "Copy" }, control: "text" as const,
126
+ ...(binding === "comment" ? { multiline: true } : {}),
126
127
  })),
127
128
  ],
128
129
  requiredValues: ["program"], project: projectComments,
@@ -51,6 +51,7 @@ export function mergeAttachments(groups: readonly (readonly ArtifactAttachment[]
51
51
  merged.set(key, {
52
52
  artifact: { ...item.artifact },
53
53
  open: item.open,
54
+ ...(item.location === undefined ? {} : { location: item.location }),
54
55
  });
55
56
  }
56
57
  return [...merged.values()]