spawnfile 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.
Files changed (198) hide show
  1. package/README.md +13 -2
  2. package/dist/cli/lifecycleCommands.js +101 -1
  3. package/dist/cli/modelCommands.js +3 -0
  4. package/dist/cli/resolveCommandInput.d.ts +19 -0
  5. package/dist/cli/resolveCommandInput.js +25 -0
  6. package/dist/cli/runCli.d.ts +4 -1
  7. package/dist/cli/runCli.js +37 -13
  8. package/dist/cli/runtimeCommands.js +1 -0
  9. package/dist/cli/statusCommand.d.ts +42 -0
  10. package/dist/cli/statusCommand.js +394 -0
  11. package/dist/cli/surfaceCommands.js +4 -0
  12. package/dist/compiler/agentSurfaces.js +2 -4
  13. package/dist/compiler/buildCompilePlanTeams.js +3 -1
  14. package/dist/compiler/compileProject.js +19 -10
  15. package/dist/compiler/compileProjectSupport.d.ts +1 -0
  16. package/dist/compiler/compileProjectSupport.js +1 -1
  17. package/dist/compiler/containerArtifacts.d.ts +1 -0
  18. package/dist/compiler/containerArtifacts.js +208 -32
  19. package/dist/compiler/containerArtifactsPlans.js +7 -4
  20. package/dist/compiler/containerArtifactsRender.d.ts +7 -1
  21. package/dist/compiler/containerArtifactsRender.js +6 -0
  22. package/dist/compiler/containerArtifactsTypes.d.ts +9 -0
  23. package/dist/compiler/containerEntrypointRender.js +18 -31
  24. package/dist/compiler/index.d.ts +1 -0
  25. package/dist/compiler/index.js +1 -0
  26. package/dist/compiler/moltnetArtifacts.d.ts +2 -0
  27. package/dist/compiler/moltnetArtifacts.js +58 -99
  28. package/dist/compiler/moltnetClientConfig.d.ts +2 -2
  29. package/dist/compiler/moltnetClientConfig.js +16 -23
  30. package/dist/compiler/moltnetConfigLowering.d.ts +3 -0
  31. package/dist/compiler/moltnetConfigLowering.js +13 -2
  32. package/dist/compiler/moltnetNodeConfig.d.ts +27 -0
  33. package/dist/compiler/moltnetNodeConfig.js +68 -0
  34. package/dist/compiler/moltnetResolution.js +1 -1
  35. package/dist/compiler/moltnetRoomMemberships.js +1 -2
  36. package/dist/compiler/moltnetRoomPolicyCompatibility.d.ts +4 -0
  37. package/dist/compiler/moltnetRoomPolicyCompatibility.js +28 -0
  38. package/dist/compiler/moltnetRuntimeConfig.d.ts +1 -1
  39. package/dist/compiler/moltnetRuntimeConfig.js +1 -13
  40. package/dist/compiler/networkBinding.d.ts +14 -0
  41. package/dist/compiler/networkBinding.js +34 -0
  42. package/dist/compiler/publishProject.d.ts +13 -0
  43. package/dist/compiler/publishProject.js +72 -0
  44. package/dist/compiler/runProject.d.ts +26 -12
  45. package/dist/compiler/runProject.js +102 -125
  46. package/dist/compiler/runProjectAuth.d.ts +5 -0
  47. package/dist/compiler/runProjectAuth.js +98 -0
  48. package/dist/compiler/runProjectDocker.d.ts +19 -0
  49. package/dist/compiler/runProjectDocker.js +57 -0
  50. package/dist/compiler/types.d.ts +3 -2
  51. package/dist/compiler/upProject.d.ts +6 -0
  52. package/dist/compiler/upProject.js +42 -8
  53. package/dist/compiler/view/buildOrganizationView.js +27 -3
  54. package/dist/compiler/view/declarations.d.ts +4 -0
  55. package/dist/compiler/view/declarations.js +104 -0
  56. package/dist/compiler/view/renderNetworks.js +12 -3
  57. package/dist/compiler/view/renderTree.js +16 -3
  58. package/dist/compiler/view/types.d.ts +77 -1
  59. package/dist/deployment/dockerInspect.d.ts +29 -0
  60. package/dist/deployment/dockerInspect.js +130 -0
  61. package/dist/deployment/dockerLabels.d.ts +16 -0
  62. package/dist/deployment/dockerLabels.js +32 -0
  63. package/dist/deployment/dockerLogs.d.ts +24 -0
  64. package/dist/deployment/dockerLogs.js +96 -0
  65. package/dist/deployment/dockerManager.d.ts +59 -0
  66. package/dist/deployment/dockerManager.js +76 -0
  67. package/dist/deployment/dockerProbeGateway.d.ts +16 -0
  68. package/dist/deployment/dockerProbeGateway.js +48 -0
  69. package/dist/deployment/homeStore.d.ts +19 -0
  70. package/dist/deployment/homeStore.js +130 -0
  71. package/dist/deployment/index.d.ts +9 -0
  72. package/dist/deployment/index.js +9 -0
  73. package/dist/deployment/names.d.ts +3 -0
  74. package/dist/deployment/names.js +12 -0
  75. package/dist/deployment/record.d.ts +76 -0
  76. package/dist/deployment/record.js +174 -0
  77. package/dist/deployment/target.d.ts +35 -0
  78. package/dist/deployment/target.js +96 -0
  79. package/dist/distribution/buildDistributionReport.d.ts +22 -0
  80. package/dist/distribution/buildDistributionReport.js +51 -0
  81. package/dist/distribution/consumeImage.d.ts +29 -0
  82. package/dist/distribution/consumeImage.js +254 -0
  83. package/dist/distribution/consumeImageSupport.d.ts +18 -0
  84. package/dist/distribution/consumeImageSupport.js +63 -0
  85. package/dist/distribution/distributionReportSchema.d.ts +84 -0
  86. package/dist/distribution/distributionReportSchema.js +107 -0
  87. package/dist/distribution/dockerRunner.d.ts +5 -0
  88. package/dist/distribution/dockerRunner.js +21 -0
  89. package/dist/distribution/extractImage.d.ts +25 -0
  90. package/dist/distribution/extractImage.js +96 -0
  91. package/dist/distribution/fingerprint.d.ts +2 -0
  92. package/dist/distribution/fingerprint.js +14 -0
  93. package/dist/distribution/imageRef.d.ts +9 -0
  94. package/dist/distribution/imageRef.js +65 -0
  95. package/dist/distribution/imageRuntimeAuth.d.ts +19 -0
  96. package/dist/distribution/imageRuntimeAuth.js +71 -0
  97. package/dist/distribution/index.d.ts +24 -0
  98. package/dist/distribution/index.js +16 -0
  99. package/dist/distribution/preflight.d.ts +11 -0
  100. package/dist/distribution/preflight.js +65 -0
  101. package/dist/distribution/projectImageView.d.ts +8 -0
  102. package/dist/distribution/projectImageView.js +83 -0
  103. package/dist/distribution/projectName.d.ts +1 -0
  104. package/dist/distribution/projectName.js +12 -0
  105. package/dist/distribution/renderImageInterface.d.ts +30 -0
  106. package/dist/distribution/renderImageInterface.js +86 -0
  107. package/dist/distribution/tarReader.d.ts +10 -0
  108. package/dist/distribution/tarReader.js +69 -0
  109. package/dist/distribution/types.d.ts +80 -0
  110. package/dist/distribution/types.js +4 -0
  111. package/dist/distribution/verifyDistributionReport.d.ts +12 -0
  112. package/dist/distribution/verifyDistributionReport.js +29 -0
  113. package/dist/filesystem/paths.d.ts +6 -0
  114. package/dist/filesystem/paths.js +11 -0
  115. package/dist/manifest/loadManifest.js +22 -2
  116. package/dist/manifest/renderSpawnfile.js +2 -4
  117. package/dist/manifest/renderSpawnfileNetworks.js +5 -0
  118. package/dist/manifest/schemas.d.ts +71 -41
  119. package/dist/manifest/surfaceSchemas.d.ts +19 -47
  120. package/dist/manifest/surfaceSchemas.js +3 -6
  121. package/dist/manifest/teamNetworkAccessSchemas.d.ts +15 -0
  122. package/dist/manifest/teamNetworkAccessSchemas.js +8 -0
  123. package/dist/manifest/teamNetworkConsoleSchemas.d.ts +7 -0
  124. package/dist/manifest/teamNetworkConsoleSchemas.js +19 -0
  125. package/dist/manifest/teamNetworkSchemas.d.ts +60 -0
  126. package/dist/manifest/teamNetworkSchemas.js +20 -7
  127. package/dist/report/createReport.d.ts +7 -1
  128. package/dist/report/createReport.js +28 -1
  129. package/dist/report/types.d.ts +43 -0
  130. package/dist/runtime/container.js +0 -16
  131. package/dist/runtime/index.d.ts +1 -0
  132. package/dist/runtime/index.js +1 -0
  133. package/dist/runtime/install.d.ts +0 -10
  134. package/dist/runtime/install.js +0 -45
  135. package/dist/runtime/openclaw/adapter.js +9 -1
  136. package/dist/runtime/openclaw/configAuth.d.ts +15 -0
  137. package/dist/runtime/openclaw/configAuth.js +46 -0
  138. package/dist/runtime/openclaw/runAuth.d.ts +6 -0
  139. package/dist/runtime/openclaw/runAuth.js +7 -55
  140. package/dist/runtime/openclaw/statusProbes.d.ts +2 -0
  141. package/dist/runtime/openclaw/statusProbes.js +31 -0
  142. package/dist/runtime/picoclaw/adapter.js +10 -0
  143. package/dist/runtime/picoclaw/runAuth.d.ts +7 -0
  144. package/dist/runtime/picoclaw/runAuth.js +8 -68
  145. package/dist/runtime/picoclaw/statusProbes.d.ts +2 -0
  146. package/dist/runtime/picoclaw/statusProbes.js +100 -0
  147. package/dist/runtime/registry.d.ts +0 -5
  148. package/dist/runtime/registry.js +2 -12
  149. package/dist/runtime/statusProbes.d.ts +16 -0
  150. package/dist/runtime/statusProbes.js +41 -0
  151. package/dist/runtime/types.d.ts +52 -1
  152. package/dist/shared/errors.d.ts +6 -0
  153. package/dist/shared/errors.js +9 -0
  154. package/dist/status/buildStatus.d.ts +13 -0
  155. package/dist/status/buildStatus.js +266 -0
  156. package/dist/status/compileReport.d.ts +96 -0
  157. package/dist/status/compileReport.js +230 -0
  158. package/dist/status/compiledContainerObservations.d.ts +5 -0
  159. package/dist/status/compiledContainerObservations.js +63 -0
  160. package/dist/status/deploymentLogs.d.ts +12 -0
  161. package/dist/status/deploymentLogs.js +45 -0
  162. package/dist/status/deployments.d.ts +14 -0
  163. package/dist/status/deployments.js +128 -0
  164. package/dist/status/imageStatusReport.d.ts +10 -0
  165. package/dist/status/imageStatusReport.js +73 -0
  166. package/dist/status/index.d.ts +14 -0
  167. package/dist/status/index.js +14 -0
  168. package/dist/status/moltnetProbes.d.ts +21 -0
  169. package/dist/status/moltnetProbes.js +240 -0
  170. package/dist/status/registryDrift.d.ts +28 -0
  171. package/dist/status/registryDrift.js +119 -0
  172. package/dist/status/renderStatus.d.ts +2 -0
  173. package/dist/status/renderStatus.js +230 -0
  174. package/dist/status/runtimeProbes.d.ts +13 -0
  175. package/dist/status/runtimeProbes.js +111 -0
  176. package/dist/status/selectionSubjects.d.ts +8 -0
  177. package/dist/status/selectionSubjects.js +77 -0
  178. package/dist/status/selectors.d.ts +14 -0
  179. package/dist/status/selectors.js +90 -0
  180. package/dist/status/traversal.d.ts +6 -0
  181. package/dist/status/traversal.js +15 -0
  182. package/dist/status/types.d.ts +113 -0
  183. package/dist/status/types.js +1 -0
  184. package/package.json +4 -2
  185. package/runtimes.yaml +22 -15
  186. package/dist/runtime/tinyclaw/adapter.d.ts +0 -2
  187. package/dist/runtime/tinyclaw/adapter.js +0 -284
  188. package/dist/runtime/tinyclaw/runAuth.d.ts +0 -2
  189. package/dist/runtime/tinyclaw/runAuth.js +0 -49
  190. package/dist/runtime/tinyclaw/scaffold-assets/AGENTS.md +0 -160
  191. package/dist/runtime/tinyclaw/scaffold-assets/CLAUDE.md +0 -5
  192. package/dist/runtime/tinyclaw/scaffold-assets/SOUL.md +0 -177
  193. package/dist/runtime/tinyclaw/scaffold.d.ts +0 -2
  194. package/dist/runtime/tinyclaw/scaffold.js +0 -24
  195. package/dist/runtime/tinyclaw/schedules.d.ts +0 -9
  196. package/dist/runtime/tinyclaw/schedules.js +0 -62
  197. package/dist/runtime/tinyclaw/surfaces.d.ts +0 -8
  198. package/dist/runtime/tinyclaw/surfaces.js +0 -94
@@ -0,0 +1,96 @@
1
+ import { SpawnfileError } from "../shared/index.js";
2
+ import { createConsumerDockerRunner } from "./dockerRunner.js";
3
+ import { parseDistributionReport } from "./distributionReportSchema.js";
4
+ import { extractSingleFileFromTar } from "./tarReader.js";
5
+ import { DISTRIBUTION_REPORT_IMAGE_PATH, IMAGE_CONTRACT_VERSION } from "./types.js";
6
+ const REPORT_SIZE_CAP_BYTES = 4 * 1024 * 1024;
7
+ const LABEL_VALUE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/;
8
+ export const resolveDockerBaseArgs = (options) => {
9
+ if (options.dockerContext) {
10
+ return ["--context", options.dockerContext];
11
+ }
12
+ if (options.dockerHost) {
13
+ return ["--host", options.dockerHost];
14
+ }
15
+ return [];
16
+ };
17
+ const assertContractLabels = (labels) => {
18
+ const contract = labels["com.spawnfile.image_contract"];
19
+ if (!contract) {
20
+ throw new SpawnfileError("validation_error", "Image is not a Spawnfile image: missing com.spawnfile.image_contract label");
21
+ }
22
+ if (contract !== IMAGE_CONTRACT_VERSION) {
23
+ throw new SpawnfileError("validation_error", `Unsupported image contract ${contract}; this CLI supports ${IMAGE_CONTRACT_VERSION}`);
24
+ }
25
+ for (const key of ["com.spawnfile.project", "com.spawnfile.compile_fingerprint"]) {
26
+ const value = labels[key];
27
+ if (!value || !LABEL_VALUE_PATTERN.test(value)) {
28
+ throw new SpawnfileError("validation_error", `Image label ${key} is missing or not identifier-only`);
29
+ }
30
+ }
31
+ };
32
+ /**
33
+ * Pulls (optionally), inspects labels, and extracts the embedded distribution
34
+ * report from an image without starting its entrypoint. Uses a stopped helper
35
+ * container and guarantees cleanup.
36
+ */
37
+ export const extractImageReport = async (imageRef, options = {}) => {
38
+ const dockerCommand = options.dockerCommand ?? "docker";
39
+ const baseArgs = resolveDockerBaseArgs(options);
40
+ const runDocker = options.runDocker ?? createConsumerDockerRunner(dockerCommand, baseArgs);
41
+ if (options.pull) {
42
+ await runDocker(["pull", imageRef]);
43
+ }
44
+ let labelsRaw;
45
+ try {
46
+ labelsRaw = (await runDocker([
47
+ "image",
48
+ "inspect",
49
+ "--format",
50
+ "{{json .Config.Labels}}",
51
+ imageRef
52
+ ])).toString("utf8").trim();
53
+ }
54
+ catch (error) {
55
+ const message = error instanceof Error ? error.message : String(error);
56
+ if (/No such image|not found/i.test(message)) {
57
+ throw new SpawnfileError("validation_error", `Image ${imageRef} is not available locally. Pull it first with --pull, or publish it with 'spawnfile publish'.`);
58
+ }
59
+ throw error;
60
+ }
61
+ let labels;
62
+ try {
63
+ labels = JSON.parse(labelsRaw) ?? {};
64
+ }
65
+ catch {
66
+ throw new SpawnfileError("validation_error", `Unable to read labels for image ${imageRef}`);
67
+ }
68
+ assertContractLabels(labels);
69
+ const reportPath = labels["com.spawnfile.report"] ?? DISTRIBUTION_REPORT_IMAGE_PATH;
70
+ const helperName = `spawnfile-inspect-${Date.now().toString(36)}-${Math.floor(Number(process.pid)).toString(36)}`;
71
+ await runDocker(["create", "--name", helperName, imageRef]);
72
+ let tar;
73
+ try {
74
+ tar = await runDocker(["cp", `${helperName}:${reportPath}`, "-"]);
75
+ }
76
+ finally {
77
+ await runDocker(["rm", "-f", helperName]).catch(() => undefined);
78
+ }
79
+ const reportBytes = extractSingleFileFromTar(tar, { maxBytes: REPORT_SIZE_CAP_BYTES });
80
+ let parsedJson;
81
+ try {
82
+ parsedJson = JSON.parse(reportBytes.toString("utf8"));
83
+ }
84
+ catch {
85
+ throw new SpawnfileError("validation_error", `Embedded report in ${imageRef} is not valid JSON`);
86
+ }
87
+ const report = parseDistributionReport(parsedJson, `embedded report in ${imageRef}`);
88
+ if (report.compile_fingerprint !== labels["com.spawnfile.compile_fingerprint"]) {
89
+ throw new SpawnfileError("validation_error", `Embedded report fingerprint does not match the image label for ${imageRef}`);
90
+ }
91
+ return {
92
+ compileFingerprint: report.compile_fingerprint,
93
+ labels,
94
+ report
95
+ };
96
+ };
@@ -0,0 +1,2 @@
1
+ import type { DistributionReport } from "./types.js";
2
+ export declare const createDistributionFingerprint: (report: Omit<DistributionReport, "compile_fingerprint" | "generated_at">) => string;
@@ -0,0 +1,14 @@
1
+ import { createHash } from "node:crypto";
2
+ const stableStringify = (value) => {
3
+ if (Array.isArray(value)) {
4
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
5
+ }
6
+ if (value && typeof value === "object") {
7
+ return `{${Object.entries(value)
8
+ .sort(([left], [right]) => left.localeCompare(right))
9
+ .map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`)
10
+ .join(",")}}`;
11
+ }
12
+ return JSON.stringify(value);
13
+ };
14
+ export const createDistributionFingerprint = (report) => `sf1:${createHash("sha1").update(stableStringify(report)).digest("hex").slice(0, 12)}`;
@@ -0,0 +1,9 @@
1
+ export interface ParsedImageReference {
2
+ digest: string | null;
3
+ name: string;
4
+ registry: string | null;
5
+ tag: string | null;
6
+ }
7
+ export declare const parseImageReference: (value: string) => ParsedImageReference | null;
8
+ export declare const parseImplicitImageReference: (value: string) => ParsedImageReference | null;
9
+ export declare const hasRegistryComponent: (value: string) => boolean;
@@ -0,0 +1,65 @@
1
+ const NAME_COMPONENT = /^[a-z0-9]+(?:(?:[._]|__|[-]+)[a-z0-9]+)*$/;
2
+ const TAG_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$/;
3
+ const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
4
+ const REGISTRY_HOST_PATTERN = /^[a-zA-Z0-9.-]+(?::\d{1,5})?$/;
5
+ const isRegistryComponent = (component) => (component.includes(".") || component.includes(":") || component === "localhost")
6
+ && REGISTRY_HOST_PATTERN.test(component);
7
+ const splitDigest = (value) => {
8
+ const index = value.indexOf("@");
9
+ if (index === -1) {
10
+ return { digest: null, rest: value };
11
+ }
12
+ return { digest: value.slice(index + 1), rest: value.slice(0, index) };
13
+ };
14
+ const splitTag = (value) => {
15
+ const slashIndex = value.lastIndexOf("/");
16
+ const colonIndex = value.lastIndexOf(":");
17
+ if (colonIndex === -1 || colonIndex < slashIndex) {
18
+ return { rest: value, tag: null };
19
+ }
20
+ return { rest: value.slice(0, colonIndex), tag: value.slice(colonIndex + 1) };
21
+ };
22
+ export const parseImageReference = (value) => {
23
+ const trimmed = value.trim();
24
+ if (!trimmed || /\s/.test(trimmed)) {
25
+ return null;
26
+ }
27
+ const { digest, rest: withoutDigest } = splitDigest(trimmed);
28
+ if (digest !== null && !DIGEST_PATTERN.test(digest)) {
29
+ return null;
30
+ }
31
+ const { rest: repository, tag } = splitTag(withoutDigest);
32
+ if (tag !== null && !TAG_PATTERN.test(tag)) {
33
+ return null;
34
+ }
35
+ if (!repository) {
36
+ return null;
37
+ }
38
+ const components = repository.split("/");
39
+ let registry = null;
40
+ let nameComponents = components;
41
+ if (components.length > 1 && isRegistryComponent(components[0])) {
42
+ registry = components[0];
43
+ nameComponents = components.slice(1);
44
+ }
45
+ if (nameComponents.length === 0 || nameComponents.some((component) => !NAME_COMPONENT.test(component))) {
46
+ return null;
47
+ }
48
+ return {
49
+ digest,
50
+ name: nameComponents.join("/"),
51
+ registry,
52
+ tag
53
+ };
54
+ };
55
+ export const parseImplicitImageReference = (value) => {
56
+ const parsed = parseImageReference(value);
57
+ if (!parsed) {
58
+ return null;
59
+ }
60
+ if (parsed.digest === null && parsed.tag === null && parsed.registry === null) {
61
+ return null;
62
+ }
63
+ return parsed;
64
+ };
65
+ export const hasRegistryComponent = (value) => parseImageReference(value)?.registry !== null;
@@ -0,0 +1,19 @@
1
+ import type { ResolvedAuthProfile } from "../auth/index.js";
2
+ import type { DistributionReport } from "./types.js";
3
+ export interface ImageRuntimeAuthInput {
4
+ authProfile: ResolvedAuthProfile;
5
+ report: DistributionReport;
6
+ tempRoot: string;
7
+ }
8
+ export interface ImageRuntimeAuthResult {
9
+ coveredModelSecrets: Set<string>;
10
+ mountArgs: string[];
11
+ }
12
+ /**
13
+ * Builds the credential mounts for a sourceless image deployment that uses
14
+ * import-based model auth. The OAuth-mode config is already baked into the
15
+ * image, so this only mounts the consumer's credential tokens (per-adapter) and
16
+ * their raw import directories into each runtime home — the same material a
17
+ * project deployment provides, without needing the project source.
18
+ */
19
+ export declare const prepareImageRuntimeAuthMounts: (input: ImageRuntimeAuthInput) => Promise<ImageRuntimeAuthResult>;
@@ -0,0 +1,71 @@
1
+ import path from "node:path";
2
+ import { loadImportedClaudeCodeCredential, loadImportedCodexCredential } from "../auth/index.js";
3
+ import { fileExists } from "../filesystem/index.js";
4
+ import { getRuntimeAdapter } from "../runtime/index.js";
5
+ import { SpawnfileError } from "../shared/index.js";
6
+ const importMountTargetName = (kind) => kind === "claude-code" ? ".claude" : ".codex";
7
+ /**
8
+ * Builds the credential mounts for a sourceless image deployment that uses
9
+ * import-based model auth. The OAuth-mode config is already baked into the
10
+ * image, so this only mounts the consumer's credential tokens (per-adapter) and
11
+ * their raw import directories into each runtime home — the same material a
12
+ * project deployment provides, without needing the project source.
13
+ */
14
+ export const prepareImageRuntimeAuthMounts = async (input) => {
15
+ const coveredModelSecrets = new Set();
16
+ const mountArgs = [];
17
+ const runtimeHomes = new Set();
18
+ for (const instance of input.report.runtime_instances) {
19
+ if (instance.home_path) {
20
+ runtimeHomes.add(instance.home_path);
21
+ }
22
+ const adapter = getRuntimeAdapter(instance.runtime);
23
+ if (!adapter.prepareRuntimeAuth) {
24
+ continue;
25
+ }
26
+ const prepared = await adapter.prepareRuntimeAuth({
27
+ authProfile: input.authProfile,
28
+ env: {},
29
+ instance: {
30
+ config_path: instance.config_path,
31
+ home_path: instance.home_path,
32
+ id: instance.id,
33
+ model_auth_methods: instance.model_auth_methods,
34
+ model_secrets_required: instance.model_secrets_required,
35
+ runtime: instance.runtime
36
+ },
37
+ outputDirectory: "",
38
+ tempRoot: input.tempRoot
39
+ });
40
+ for (const secret of prepared.coveredModelSecrets) {
41
+ coveredModelSecrets.add(secret);
42
+ }
43
+ mountArgs.push(...prepared.mountArgs);
44
+ }
45
+ // Mount the raw credential import directories into each runtime home so the
46
+ // runtime's OAuth client can read them (e.g. ~/.claude, ~/.codex).
47
+ for (const kind of ["claude-code", "codex"]) {
48
+ const entry = input.authProfile.imports[kind];
49
+ if (!entry) {
50
+ continue;
51
+ }
52
+ if (!(await fileExists(entry.path))) {
53
+ throw new SpawnfileError("validation_error", `Imported auth path for ${kind} does not exist: ${entry.path}`);
54
+ }
55
+ // The directory existing is not enough: a registered import whose credential
56
+ // file is missing, expired, or malformed would mount cleanly but produce a
57
+ // container that cannot authenticate. Load it now so the failure surfaces
58
+ // before the container starts, mirroring the project-deployment path.
59
+ const credential = kind === "claude-code"
60
+ ? await loadImportedClaudeCodeCredential(entry.path)
61
+ : await loadImportedCodexCredential(entry.path);
62
+ if (!credential) {
63
+ throw new SpawnfileError("validation_error", `Imported ${kind} auth at ${entry.path} has no usable credential. ` +
64
+ `Re-run the ${kind} login (or re-import) before deploying this image.`);
65
+ }
66
+ for (const home of runtimeHomes) {
67
+ mountArgs.push("-v", `${entry.path}:${path.posix.join(home, importMountTargetName(kind))}`);
68
+ }
69
+ }
70
+ return { coveredModelSecrets, mountArgs };
71
+ };
@@ -0,0 +1,24 @@
1
+ export { buildDistributionReport, createDistributionImageLabels } from "./buildDistributionReport.js";
2
+ export type { BuildDistributionReportInput, DistributionEnvVariableInput } from "./buildDistributionReport.js";
3
+ export { createDistributionFingerprint } from "./fingerprint.js";
4
+ export { extractImageReport, resolveDockerBaseArgs } from "./extractImage.js";
5
+ export type { DockerCommandRunner, ExtractImageOptions, ImageInspection } from "./extractImage.js";
6
+ export { parseDistributionReport } from "./distributionReportSchema.js";
7
+ export { consumeImageUp } from "./consumeImage.js";
8
+ export type { ConsumeImageUpOptions, ConsumeImageUpResult } from "./consumeImage.js";
9
+ export { deriveDeploymentName, deriveVolumeName, renderEnvFileContent, resolveImageEnvironment } from "./consumeImageSupport.js";
10
+ export { createConsumerDockerRunner } from "./dockerRunner.js";
11
+ export { prepareImageRuntimeAuthMounts } from "./imageRuntimeAuth.js";
12
+ export { runImagePreflight } from "./preflight.js";
13
+ export { verifyDistributionReport } from "./verifyDistributionReport.js";
14
+ export type { VerifyDistributionReportInput } from "./verifyDistributionReport.js";
15
+ export { projectImageOrganizationView } from "./projectImageView.js";
16
+ export { buildImageInterfaceSummary, renderImageInterface } from "./renderImageInterface.js";
17
+ export type { ImageInterfaceSummary, RenderImageInterfaceOptions } from "./renderImageInterface.js";
18
+ export type { PreflightInput, PreflightResult } from "./preflight.js";
19
+ export { extractSingleFileFromTar } from "./tarReader.js";
20
+ export { hasRegistryComponent, parseImageReference, parseImplicitImageReference } from "./imageRef.js";
21
+ export type { ParsedImageReference } from "./imageRef.js";
22
+ export { normalizeProjectLabelSlug } from "./projectName.js";
23
+ export { DISTRIBUTION_REPORT_IMAGE_PATH, DISTRIBUTION_REPORT_OUTPUT_FILE, DISTRIBUTION_REPORT_VERSION, IMAGE_CONTRACT_VERSION } from "./types.js";
24
+ export type { DistributionAgentSummary, DistributionImageLabels, DistributionMoltnetNetwork, DistributionOrganizationSummary, DistributionPersistentMount, DistributionPortMapping, DistributionReport, DistributionRuntimeInstance, DistributionSecretCategory, DistributionSecretEntry, DistributionTeamSummary, DistributionWorkspaceResource } from "./types.js";
@@ -0,0 +1,16 @@
1
+ export { buildDistributionReport, createDistributionImageLabels } from "./buildDistributionReport.js";
2
+ export { createDistributionFingerprint } from "./fingerprint.js";
3
+ export { extractImageReport, resolveDockerBaseArgs } from "./extractImage.js";
4
+ export { parseDistributionReport } from "./distributionReportSchema.js";
5
+ export { consumeImageUp } from "./consumeImage.js";
6
+ export { deriveDeploymentName, deriveVolumeName, renderEnvFileContent, resolveImageEnvironment } from "./consumeImageSupport.js";
7
+ export { createConsumerDockerRunner } from "./dockerRunner.js";
8
+ export { prepareImageRuntimeAuthMounts } from "./imageRuntimeAuth.js";
9
+ export { runImagePreflight } from "./preflight.js";
10
+ export { verifyDistributionReport } from "./verifyDistributionReport.js";
11
+ export { projectImageOrganizationView } from "./projectImageView.js";
12
+ export { buildImageInterfaceSummary, renderImageInterface } from "./renderImageInterface.js";
13
+ export { extractSingleFileFromTar } from "./tarReader.js";
14
+ export { hasRegistryComponent, parseImageReference, parseImplicitImageReference } from "./imageRef.js";
15
+ export { normalizeProjectLabelSlug } from "./projectName.js";
16
+ export { DISTRIBUTION_REPORT_IMAGE_PATH, DISTRIBUTION_REPORT_OUTPUT_FILE, DISTRIBUTION_REPORT_VERSION, IMAGE_CONTRACT_VERSION } from "./types.js";
@@ -0,0 +1,11 @@
1
+ import type { DistributionReport } from "./types.js";
2
+ export interface PreflightInput {
3
+ authValues: Record<string, string>;
4
+ availableImports?: string[];
5
+ report: DistributionReport;
6
+ }
7
+ export interface PreflightResult {
8
+ generatedSecrets: string[];
9
+ requiredSecrets: string[];
10
+ }
11
+ export declare const runImagePreflight: (input: PreflightInput) => PreflightResult;
@@ -0,0 +1,65 @@
1
+ import { SpawnfileError } from "../shared/index.js";
2
+ const ENV_BASED_AUTH_METHODS = new Set(["api_key", "none"]);
3
+ // Import-based auth methods are satisfiable sourceless when the consumer has the
4
+ // matching local credential import (their logged-in Claude Code / Codex session).
5
+ const IMPORT_AUTH_METHOD_KINDS = {
6
+ "claude-code": "claude-code",
7
+ codex: "codex"
8
+ };
9
+ const collectUnsupportedAuth = (report, availableImports) => {
10
+ const unsupported = [];
11
+ for (const instance of report.runtime_instances) {
12
+ for (const [provider, method] of Object.entries(instance.model_auth_methods)) {
13
+ if (ENV_BASED_AUTH_METHODS.has(method)) {
14
+ continue;
15
+ }
16
+ const importKind = IMPORT_AUTH_METHOD_KINDS[method];
17
+ if (importKind && availableImports.has(importKind)) {
18
+ continue;
19
+ }
20
+ unsupported.push({ instance: instance.id, method, provider, runtime: instance.runtime });
21
+ }
22
+ }
23
+ return unsupported;
24
+ };
25
+ export const runImagePreflight = (input) => {
26
+ const { authValues, report } = input;
27
+ const availableImports = new Set(input.availableImports ?? []);
28
+ const unsupportedAuth = collectUnsupportedAuth(report, availableImports);
29
+ if (unsupportedAuth.length > 0) {
30
+ const detail = unsupportedAuth
31
+ .map((entry) => `${entry.runtime}/${entry.instance} (${entry.provider}: ${entry.method})`)
32
+ .sort()
33
+ .join(", ");
34
+ throw new SpawnfileError("validation_error", `This image needs model auth this deployment cannot provide: ${detail}. ` +
35
+ "Provide an api_key secret, or an auth profile with the matching claude-code/codex import.");
36
+ }
37
+ const categories = ["model", "project", "runtime", "surface"];
38
+ const requiredSecrets = new Set();
39
+ const generatedSecrets = new Set();
40
+ for (const category of categories) {
41
+ for (const entry of report.secrets[category]) {
42
+ if (entry.generated) {
43
+ generatedSecrets.add(entry.name);
44
+ continue;
45
+ }
46
+ if (entry.required) {
47
+ requiredSecrets.add(entry.name);
48
+ }
49
+ }
50
+ }
51
+ const missing = [...requiredSecrets]
52
+ .filter((name) => {
53
+ const value = authValues[name];
54
+ return typeof value !== "string" || value.length === 0;
55
+ })
56
+ .sort();
57
+ if (missing.length > 0) {
58
+ throw new SpawnfileError("validation_error", `Missing required secrets for this image: ${missing.join(", ")}. ` +
59
+ "Provide them through the selected auth profile or env file.");
60
+ }
61
+ return {
62
+ generatedSecrets: [...generatedSecrets].sort(),
63
+ requiredSecrets: [...requiredSecrets].sort()
64
+ };
65
+ };
@@ -0,0 +1,8 @@
1
+ import type { OrganizationView } from "../compiler/index.js";
2
+ import type { DistributionReport } from "./types.js";
3
+ /**
4
+ * Projects a distribution report into an OrganizationView so sourceless image
5
+ * status can reuse the existing renderer. There is no source, so the declared
6
+ * layer is intentionally sparse; node sources use an image placeholder.
7
+ */
8
+ export declare const projectImageOrganizationView: (report: DistributionReport, inputPath: string) => OrganizationView;
@@ -0,0 +1,83 @@
1
+ const IMAGE_SOURCE_PLACEHOLDER = "<image>";
2
+ const buildAgentNode = (agent) => ({
3
+ children: [],
4
+ displayName: agent.name,
5
+ id: agent.id,
6
+ kind: "agent",
7
+ name: agent.name,
8
+ runtimeName: agent.runtime,
9
+ source: IMAGE_SOURCE_PLACEHOLDER
10
+ });
11
+ /**
12
+ * Projects a distribution report into an OrganizationView so sourceless image
13
+ * status can reuse the existing renderer. There is no source, so the declared
14
+ * layer is intentionally sparse; node sources use an image placeholder.
15
+ */
16
+ export const projectImageOrganizationView = (report, inputPath) => {
17
+ const agentsById = new Map(report.organization.agents.map((agent) => [agent.id, agent]));
18
+ const teams = report.organization.teams;
19
+ const networks = report.moltnet.networks.map((network) => ({
20
+ declaringTeamName: report.organization.project,
21
+ declaringTeamSource: IMAGE_SOURCE_PLACEHOLDER,
22
+ id: network.id,
23
+ name: network.id,
24
+ provider: "moltnet",
25
+ rooms: [],
26
+ serverMode: network.server_mode
27
+ }));
28
+ const runtimes = [
29
+ ...new Map(report.runtime_instances.map((instance) => [
30
+ instance.runtime,
31
+ {
32
+ name: instance.runtime,
33
+ nodeIds: report.runtime_instances
34
+ .filter((other) => other.runtime === instance.runtime)
35
+ .flatMap((other) => other.node_ids)
36
+ .sort()
37
+ }
38
+ ])).values()
39
+ ].sort((left, right) => left.name.localeCompare(right.name));
40
+ let root;
41
+ if (teams.length > 0) {
42
+ const rootTeam = teams[0];
43
+ const memberEdges = rootTeam.agents
44
+ .map((agentId) => agentsById.get(agentId))
45
+ .filter((agent) => agent !== undefined)
46
+ .map((agent) => ({
47
+ label: agent.id,
48
+ node: buildAgentNode(agent),
49
+ relation: "team_member"
50
+ }));
51
+ root = {
52
+ children: memberEdges,
53
+ displayName: rootTeam.name,
54
+ id: rootTeam.id,
55
+ kind: "team",
56
+ name: rootTeam.name,
57
+ runtimeName: null,
58
+ source: IMAGE_SOURCE_PLACEHOLDER
59
+ };
60
+ }
61
+ else if (report.organization.agents.length > 0) {
62
+ root = buildAgentNode(report.organization.agents[0]);
63
+ }
64
+ else {
65
+ root = {
66
+ children: [],
67
+ displayName: report.organization.project,
68
+ id: report.organization.project,
69
+ kind: "team",
70
+ name: report.organization.project,
71
+ runtimeName: null,
72
+ source: IMAGE_SOURCE_PLACEHOLDER
73
+ };
74
+ }
75
+ return {
76
+ contexts: [],
77
+ diagnostics: [],
78
+ inputPath,
79
+ networks,
80
+ root,
81
+ runtimes
82
+ };
83
+ };
@@ -0,0 +1 @@
1
+ export declare const normalizeProjectLabelSlug: (projectName: string) => string;
@@ -0,0 +1,12 @@
1
+ import { SpawnfileError } from "../shared/index.js";
2
+ const LABEL_VALUE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/;
3
+ export const normalizeProjectLabelSlug = (projectName) => {
4
+ const slug = projectName
5
+ .replace(/[^A-Za-z0-9_.:-]+/g, "-")
6
+ .replace(/^[^A-Za-z0-9]+/, "")
7
+ .replace(/-+$/, "");
8
+ if (!slug || !LABEL_VALUE_PATTERN.test(slug)) {
9
+ throw new SpawnfileError("validation_error", `Project name ${JSON.stringify(projectName)} cannot be normalized into a label-safe slug`);
10
+ }
11
+ return slug;
12
+ };
@@ -0,0 +1,30 @@
1
+ import type { DistributionReport } from "./types.js";
2
+ export interface RenderImageInterfaceOptions {
3
+ imageRef: string;
4
+ json?: boolean;
5
+ }
6
+ export interface ImageInterfaceSummary {
7
+ agents: Array<{
8
+ id: string;
9
+ name: string;
10
+ runtime: string | null;
11
+ teams: string[];
12
+ }>;
13
+ compileFingerprint: string;
14
+ generatedAt: string;
15
+ imageRef: string;
16
+ networks: Array<{
17
+ id: string;
18
+ serverMode: string;
19
+ }>;
20
+ ports: number[];
21
+ project: string;
22
+ requiredSecrets: string[];
23
+ teams: Array<{
24
+ agents: string[];
25
+ id: string;
26
+ name: string;
27
+ }>;
28
+ }
29
+ export declare const buildImageInterfaceSummary: (report: DistributionReport, imageRef: string) => ImageInterfaceSummary;
30
+ export declare const renderImageInterface: (report: DistributionReport, options: RenderImageInterfaceOptions) => string;
@@ -0,0 +1,86 @@
1
+ export const buildImageInterfaceSummary = (report, imageRef) => {
2
+ const categories = ["model", "project", "runtime", "surface"];
3
+ const requiredSecrets = new Set();
4
+ for (const category of categories) {
5
+ for (const entry of report.secrets[category]) {
6
+ if (entry.required && !entry.generated) {
7
+ requiredSecrets.add(entry.name);
8
+ }
9
+ }
10
+ }
11
+ return {
12
+ agents: report.organization.agents.map((agent) => ({
13
+ id: agent.id,
14
+ name: agent.name,
15
+ runtime: agent.runtime,
16
+ teams: agent.teams
17
+ })),
18
+ compileFingerprint: report.compile_fingerprint,
19
+ generatedAt: report.generated_at,
20
+ imageRef,
21
+ networks: report.moltnet.networks.map((network) => ({
22
+ id: network.id,
23
+ serverMode: network.server_mode
24
+ })),
25
+ ports: [...report.ports].sort((left, right) => left - right),
26
+ project: report.organization.project,
27
+ requiredSecrets: [...requiredSecrets].sort(),
28
+ teams: report.organization.teams.map((team) => ({
29
+ agents: team.agents,
30
+ id: team.id,
31
+ name: team.name
32
+ }))
33
+ };
34
+ };
35
+ export const renderImageInterface = (report, options) => {
36
+ const summary = buildImageInterfaceSummary(report, options.imageRef);
37
+ if (options.json) {
38
+ return JSON.stringify(summary, null, 2);
39
+ }
40
+ const lines = [];
41
+ lines.push(`Image: ${summary.imageRef}`);
42
+ lines.push(`Project: ${summary.project}`);
43
+ lines.push(`Compiled: ${summary.compileFingerprint} (${summary.generatedAt})`);
44
+ lines.push("Declared status is unavailable for image references (no source).");
45
+ lines.push("");
46
+ lines.push("Agents");
47
+ for (const agent of summary.agents) {
48
+ const runtime = agent.runtime ?? "unbound";
49
+ const teams = agent.teams.length > 0 ? ` teams=${agent.teams.join(",")}` : "";
50
+ lines.push(` ${agent.id} ${runtime}${teams}`);
51
+ }
52
+ if (summary.teams.length > 0) {
53
+ lines.push("");
54
+ lines.push("Teams");
55
+ for (const team of summary.teams) {
56
+ lines.push(` ${team.id} members=${team.agents.join(",") || "none"}`);
57
+ }
58
+ }
59
+ if (summary.networks.length > 0) {
60
+ lines.push("");
61
+ lines.push("Networks");
62
+ for (const network of summary.networks) {
63
+ lines.push(` ${network.id} ${network.serverMode}`);
64
+ }
65
+ }
66
+ lines.push("");
67
+ lines.push("Required secrets");
68
+ if (summary.requiredSecrets.length > 0) {
69
+ for (const secret of summary.requiredSecrets) {
70
+ lines.push(` ${secret}`);
71
+ }
72
+ }
73
+ else {
74
+ lines.push(" none");
75
+ }
76
+ if (summary.ports.length > 0) {
77
+ lines.push("");
78
+ lines.push(`Published ports: ${summary.ports.join(", ")}`);
79
+ }
80
+ lines.push("");
81
+ lines.push(`next: spawnfile up ${summary.imageRef} --auth-profile <profile>`);
82
+ if (summary.requiredSecrets.length > 0) {
83
+ lines.push(" supply the required secrets above via that profile or --env-file");
84
+ }
85
+ return lines.join("\n");
86
+ };
@@ -0,0 +1,10 @@
1
+ export interface ExtractSingleFileOptions {
2
+ maxBytes: number;
3
+ }
4
+ /**
5
+ * Extracts exactly one regular file from a (single-file) tar stream such as the
6
+ * output of `docker cp <container>:<file> -`. Rejects symlinks, hardlinks,
7
+ * directories, path traversal, and oversized payloads, because the stream is
8
+ * untrusted input pulled from a registry.
9
+ */
10
+ export declare const extractSingleFileFromTar: (tar: Buffer, options: ExtractSingleFileOptions) => Buffer;