spawnfile 0.1.11 → 0.1.13

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 (220) hide show
  1. package/README.md +21 -3
  2. package/dist/auth/runtimeCredentials.js +18 -2
  3. package/dist/cli/devCommands.d.ts +3 -0
  4. package/dist/cli/devCommands.js +115 -0
  5. package/dist/cli/lifecycleCommands.js +103 -1
  6. package/dist/cli/modelCommands.js +3 -0
  7. package/dist/cli/resolveCommandInput.d.ts +19 -0
  8. package/dist/cli/resolveCommandInput.js +25 -0
  9. package/dist/cli/runCli.d.ts +10 -1
  10. package/dist/cli/runCli.js +41 -13
  11. package/dist/cli/runtimeCommands.js +1 -0
  12. package/dist/cli/statusCommand.d.ts +43 -0
  13. package/dist/cli/statusCommand.js +428 -0
  14. package/dist/cli/surfaceCommands.js +4 -0
  15. package/dist/compiler/agentSurfaces.js +2 -4
  16. package/dist/compiler/buildProject.d.ts +9 -1
  17. package/dist/compiler/buildProject.js +44 -5
  18. package/dist/compiler/compileProject.d.ts +2 -0
  19. package/dist/compiler/compileProject.js +22 -11
  20. package/dist/compiler/containerArtifacts.d.ts +1 -0
  21. package/dist/compiler/containerArtifacts.js +208 -32
  22. package/dist/compiler/containerArtifactsPlans.js +7 -4
  23. package/dist/compiler/containerArtifactsRender.d.ts +7 -1
  24. package/dist/compiler/containerArtifactsRender.js +31 -69
  25. package/dist/compiler/containerArtifactsTypes.d.ts +9 -0
  26. package/dist/compiler/containerEntrypointRender.js +22 -32
  27. package/dist/compiler/containerPackageRender.d.ts +15 -0
  28. package/dist/compiler/containerPackageRender.js +63 -0
  29. package/dist/compiler/containerTargetResources.js +3 -1
  30. package/dist/compiler/index.d.ts +1 -0
  31. package/dist/compiler/index.js +1 -0
  32. package/dist/compiler/moltnetArtifacts.js +21 -1
  33. package/dist/compiler/moltnetBinaries.d.ts +4 -1
  34. package/dist/compiler/moltnetBinaries.js +24 -3
  35. package/dist/compiler/moltnetClientConfig.d.ts +1 -1
  36. package/dist/compiler/moltnetClientConfig.js +14 -16
  37. package/dist/compiler/moltnetNodeConfig.js +2 -4
  38. package/dist/compiler/moltnetResolution.js +1 -1
  39. package/dist/compiler/moltnetRoomMemberships.js +1 -2
  40. package/dist/compiler/moltnetRuntimeConfig.d.ts +1 -1
  41. package/dist/compiler/moltnetRuntimeConfig.js +9 -10
  42. package/dist/compiler/networkBinding.d.ts +14 -0
  43. package/dist/compiler/networkBinding.js +34 -0
  44. package/dist/compiler/publishProject.d.ts +13 -0
  45. package/dist/compiler/publishProject.js +72 -0
  46. package/dist/compiler/runProject.d.ts +26 -12
  47. package/dist/compiler/runProject.js +109 -126
  48. package/dist/compiler/runProjectAuth.d.ts +5 -0
  49. package/dist/compiler/runProjectAuth.js +98 -0
  50. package/dist/compiler/runProjectDocker.d.ts +19 -0
  51. package/dist/compiler/runProjectDocker.js +202 -0
  52. package/dist/compiler/types.d.ts +1 -2
  53. package/dist/compiler/upProject.d.ts +6 -0
  54. package/dist/compiler/upProject.js +43 -8
  55. package/dist/compiler/view/buildOrganizationView.js +4 -1
  56. package/dist/compiler/view/declarations.d.ts +4 -0
  57. package/dist/compiler/view/declarations.js +104 -0
  58. package/dist/compiler/view/renderNetworks.js +1 -2
  59. package/dist/compiler/view/renderTree.js +6 -2
  60. package/dist/compiler/view/types.d.ts +64 -1
  61. package/dist/deployment/dockerInspect.d.ts +29 -0
  62. package/dist/deployment/dockerInspect.js +130 -0
  63. package/dist/deployment/dockerLabels.d.ts +16 -0
  64. package/dist/deployment/dockerLabels.js +32 -0
  65. package/dist/deployment/dockerLogs.d.ts +24 -0
  66. package/dist/deployment/dockerLogs.js +96 -0
  67. package/dist/deployment/dockerManager.d.ts +59 -0
  68. package/dist/deployment/dockerManager.js +76 -0
  69. package/dist/deployment/dockerProbeGateway.d.ts +16 -0
  70. package/dist/deployment/dockerProbeGateway.js +48 -0
  71. package/dist/deployment/dockerRecover.d.ts +17 -0
  72. package/dist/deployment/dockerRecover.js +129 -0
  73. package/dist/deployment/homeStore.d.ts +19 -0
  74. package/dist/deployment/homeStore.js +130 -0
  75. package/dist/deployment/index.d.ts +10 -0
  76. package/dist/deployment/index.js +10 -0
  77. package/dist/deployment/names.d.ts +3 -0
  78. package/dist/deployment/names.js +12 -0
  79. package/dist/deployment/record.d.ts +76 -0
  80. package/dist/deployment/record.js +174 -0
  81. package/dist/deployment/target.d.ts +35 -0
  82. package/dist/deployment/target.js +96 -0
  83. package/dist/dev/activity.d.ts +14 -0
  84. package/dist/dev/activity.js +48 -0
  85. package/dist/dev/docker.d.ts +16 -0
  86. package/dist/dev/docker.js +71 -0
  87. package/dist/dev/index.d.ts +2 -0
  88. package/dist/dev/index.js +2 -0
  89. package/dist/dev/project.d.ts +35 -0
  90. package/dist/dev/project.js +164 -0
  91. package/dist/distribution/buildDistributionReport.d.ts +22 -0
  92. package/dist/distribution/buildDistributionReport.js +51 -0
  93. package/dist/distribution/consumeImage.d.ts +29 -0
  94. package/dist/distribution/consumeImage.js +254 -0
  95. package/dist/distribution/consumeImageSupport.d.ts +18 -0
  96. package/dist/distribution/consumeImageSupport.js +63 -0
  97. package/dist/distribution/distributionReportSchema.d.ts +84 -0
  98. package/dist/distribution/distributionReportSchema.js +107 -0
  99. package/dist/distribution/dockerRunner.d.ts +5 -0
  100. package/dist/distribution/dockerRunner.js +21 -0
  101. package/dist/distribution/extractImage.d.ts +25 -0
  102. package/dist/distribution/extractImage.js +96 -0
  103. package/dist/distribution/fingerprint.d.ts +2 -0
  104. package/dist/distribution/fingerprint.js +14 -0
  105. package/dist/distribution/imageRef.d.ts +9 -0
  106. package/dist/distribution/imageRef.js +65 -0
  107. package/dist/distribution/imageRuntimeAuth.d.ts +19 -0
  108. package/dist/distribution/imageRuntimeAuth.js +71 -0
  109. package/dist/distribution/index.d.ts +24 -0
  110. package/dist/distribution/index.js +16 -0
  111. package/dist/distribution/preflight.d.ts +11 -0
  112. package/dist/distribution/preflight.js +65 -0
  113. package/dist/distribution/projectImageView.d.ts +8 -0
  114. package/dist/distribution/projectImageView.js +83 -0
  115. package/dist/distribution/projectName.d.ts +1 -0
  116. package/dist/distribution/projectName.js +12 -0
  117. package/dist/distribution/renderImageInterface.d.ts +30 -0
  118. package/dist/distribution/renderImageInterface.js +86 -0
  119. package/dist/distribution/tarReader.d.ts +10 -0
  120. package/dist/distribution/tarReader.js +69 -0
  121. package/dist/distribution/types.d.ts +80 -0
  122. package/dist/distribution/types.js +4 -0
  123. package/dist/distribution/verifyDistributionReport.d.ts +12 -0
  124. package/dist/distribution/verifyDistributionReport.js +29 -0
  125. package/dist/filesystem/paths.d.ts +6 -0
  126. package/dist/filesystem/paths.js +11 -0
  127. package/dist/manifest/loadManifest.js +22 -2
  128. package/dist/manifest/renderSpawnfile.js +2 -4
  129. package/dist/manifest/schemas.d.ts +17 -41
  130. package/dist/manifest/surfaceSchemas.d.ts +19 -47
  131. package/dist/manifest/surfaceSchemas.js +3 -6
  132. package/dist/report/createReport.d.ts +7 -1
  133. package/dist/report/createReport.js +28 -1
  134. package/dist/report/types.d.ts +43 -0
  135. package/dist/runtime/common.d.ts +2 -0
  136. package/dist/runtime/common.js +1 -1
  137. package/dist/runtime/container.d.ts +1 -0
  138. package/dist/runtime/container.js +82 -15
  139. package/dist/runtime/index.d.ts +1 -0
  140. package/dist/runtime/index.js +1 -0
  141. package/dist/runtime/install.d.ts +1 -11
  142. package/dist/runtime/install.js +27 -30
  143. package/dist/runtime/openclaw/adapter.js +9 -1
  144. package/dist/runtime/openclaw/configAuth.d.ts +15 -0
  145. package/dist/runtime/openclaw/configAuth.js +46 -0
  146. package/dist/runtime/openclaw/runAuth.d.ts +6 -0
  147. package/dist/runtime/openclaw/runAuth.js +7 -55
  148. package/dist/runtime/openclaw/statusProbes.d.ts +2 -0
  149. package/dist/runtime/openclaw/statusProbes.js +31 -0
  150. package/dist/runtime/pi/adapter.d.ts +4 -0
  151. package/dist/runtime/pi/adapter.js +198 -0
  152. package/dist/runtime/pi/appActivitySource.d.ts +1 -0
  153. package/dist/runtime/pi/appActivitySource.js +87 -0
  154. package/dist/runtime/pi/appCoreSource.d.ts +1 -0
  155. package/dist/runtime/pi/appCoreSource.js +354 -0
  156. package/dist/runtime/pi/appPreludeSource.d.ts +1 -0
  157. package/dist/runtime/pi/appPreludeSource.js +103 -0
  158. package/dist/runtime/pi/appSource.d.ts +1 -0
  159. package/dist/runtime/pi/appSource.js +8 -0
  160. package/dist/runtime/pi/appTemplate.d.ts +28 -0
  161. package/dist/runtime/pi/appTemplate.js +144 -0
  162. package/dist/runtime/pi/runAuth.d.ts +2 -0
  163. package/dist/runtime/pi/runAuth.js +68 -0
  164. package/dist/runtime/picoclaw/adapter.js +10 -0
  165. package/dist/runtime/picoclaw/runAuth.d.ts +7 -0
  166. package/dist/runtime/picoclaw/runAuth.js +8 -68
  167. package/dist/runtime/picoclaw/statusProbes.d.ts +2 -0
  168. package/dist/runtime/picoclaw/statusProbes.js +100 -0
  169. package/dist/runtime/registry.d.ts +0 -5
  170. package/dist/runtime/registry.js +5 -12
  171. package/dist/runtime/statusProbes.d.ts +16 -0
  172. package/dist/runtime/statusProbes.js +41 -0
  173. package/dist/runtime/types.d.ts +53 -1
  174. package/dist/shared/errors.d.ts +6 -0
  175. package/dist/shared/errors.js +9 -0
  176. package/dist/status/buildStatus.d.ts +13 -0
  177. package/dist/status/buildStatus.js +266 -0
  178. package/dist/status/compileReport.d.ts +96 -0
  179. package/dist/status/compileReport.js +230 -0
  180. package/dist/status/compiledContainerObservations.d.ts +5 -0
  181. package/dist/status/compiledContainerObservations.js +63 -0
  182. package/dist/status/deploymentLogs.d.ts +12 -0
  183. package/dist/status/deploymentLogs.js +45 -0
  184. package/dist/status/deployments.d.ts +14 -0
  185. package/dist/status/deployments.js +121 -0
  186. package/dist/status/imageStatusReport.d.ts +10 -0
  187. package/dist/status/imageStatusReport.js +73 -0
  188. package/dist/status/index.d.ts +14 -0
  189. package/dist/status/index.js +14 -0
  190. package/dist/status/moltnetProbes.d.ts +21 -0
  191. package/dist/status/moltnetProbes.js +240 -0
  192. package/dist/status/registryDrift.d.ts +28 -0
  193. package/dist/status/registryDrift.js +119 -0
  194. package/dist/status/renderStatus.d.ts +2 -0
  195. package/dist/status/renderStatus.js +230 -0
  196. package/dist/status/runtimeProbes.d.ts +13 -0
  197. package/dist/status/runtimeProbes.js +111 -0
  198. package/dist/status/selectionSubjects.d.ts +8 -0
  199. package/dist/status/selectionSubjects.js +77 -0
  200. package/dist/status/selectors.d.ts +14 -0
  201. package/dist/status/selectors.js +90 -0
  202. package/dist/status/traversal.d.ts +6 -0
  203. package/dist/status/traversal.js +15 -0
  204. package/dist/status/types.d.ts +113 -0
  205. package/dist/status/types.js +1 -0
  206. package/package.json +9 -2
  207. package/runtimes.yaml +44 -21
  208. package/dist/runtime/tinyclaw/adapter.d.ts +0 -2
  209. package/dist/runtime/tinyclaw/adapter.js +0 -284
  210. package/dist/runtime/tinyclaw/runAuth.d.ts +0 -2
  211. package/dist/runtime/tinyclaw/runAuth.js +0 -49
  212. package/dist/runtime/tinyclaw/scaffold-assets/AGENTS.md +0 -160
  213. package/dist/runtime/tinyclaw/scaffold-assets/CLAUDE.md +0 -5
  214. package/dist/runtime/tinyclaw/scaffold-assets/SOUL.md +0 -177
  215. package/dist/runtime/tinyclaw/scaffold.d.ts +0 -2
  216. package/dist/runtime/tinyclaw/scaffold.js +0 -24
  217. package/dist/runtime/tinyclaw/schedules.d.ts +0 -9
  218. package/dist/runtime/tinyclaw/schedules.js +0 -62
  219. package/dist/runtime/tinyclaw/surfaces.d.ts +0 -8
  220. package/dist/runtime/tinyclaw/surfaces.js +0 -94
@@ -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;
@@ -0,0 +1,69 @@
1
+ import { SpawnfileError } from "../shared/index.js";
2
+ const BLOCK_SIZE = 512;
3
+ const parseOctal = (buffer, offset, length) => {
4
+ const raw = buffer
5
+ .subarray(offset, offset + length)
6
+ .toString("ascii")
7
+ .replace(/\0.*$/, "")
8
+ .trim();
9
+ if (raw === "") {
10
+ return 0;
11
+ }
12
+ const parsed = Number.parseInt(raw, 8);
13
+ if (!Number.isFinite(parsed) || parsed < 0) {
14
+ throw new SpawnfileError("validation_error", "Malformed tar header: invalid numeric field");
15
+ }
16
+ return parsed;
17
+ };
18
+ const isZeroBlock = (block) => block.every((byte) => byte === 0);
19
+ /**
20
+ * Extracts exactly one regular file from a (single-file) tar stream such as the
21
+ * output of `docker cp <container>:<file> -`. Rejects symlinks, hardlinks,
22
+ * directories, path traversal, and oversized payloads, because the stream is
23
+ * untrusted input pulled from a registry.
24
+ */
25
+ export const extractSingleFileFromTar = (tar, options) => {
26
+ let offset = 0;
27
+ let result = null;
28
+ while (offset + BLOCK_SIZE <= tar.length) {
29
+ const header = tar.subarray(offset, offset + BLOCK_SIZE);
30
+ if (isZeroBlock(header)) {
31
+ break;
32
+ }
33
+ offset += BLOCK_SIZE;
34
+ const name = header.subarray(0, 100).toString("ascii").replace(/\0.*$/, "");
35
+ const size = parseOctal(header, 124, 12);
36
+ const typeFlag = String.fromCharCode(header[156] ?? 0);
37
+ if (typeFlag === "5") {
38
+ throw new SpawnfileError("validation_error", `Refusing tar directory entry: ${name}`);
39
+ }
40
+ if (typeFlag === "2" || typeFlag === "1") {
41
+ throw new SpawnfileError("validation_error", `Refusing tar link entry: ${name}`);
42
+ }
43
+ if (typeFlag !== "0" && typeFlag !== "\0") {
44
+ // Skip metadata entries (pax headers, global headers) without treating
45
+ // them as the payload.
46
+ offset += Math.ceil(size / BLOCK_SIZE) * BLOCK_SIZE;
47
+ continue;
48
+ }
49
+ if (name.includes("..")) {
50
+ throw new SpawnfileError("validation_error", `Refusing tar entry with traversal: ${name}`);
51
+ }
52
+ if (size > options.maxBytes) {
53
+ throw new SpawnfileError("validation_error", `Embedded report exceeds the ${options.maxBytes}-byte size cap`);
54
+ }
55
+ if (result) {
56
+ throw new SpawnfileError("validation_error", "Expected a single-file tar stream but found multiple file entries");
57
+ }
58
+ const contentEnd = offset + size;
59
+ if (contentEnd > tar.length) {
60
+ throw new SpawnfileError("validation_error", "Truncated tar stream");
61
+ }
62
+ result = Buffer.from(tar.subarray(offset, contentEnd));
63
+ offset += Math.ceil(size / BLOCK_SIZE) * BLOCK_SIZE;
64
+ }
65
+ if (!result) {
66
+ throw new SpawnfileError("validation_error", "No file entry found in tar stream");
67
+ }
68
+ return result;
69
+ };
@@ -0,0 +1,80 @@
1
+ import type { ModelAuthMethod } from "../shared/index.js";
2
+ export type DistributionSecretCategory = "model" | "project" | "runtime" | "surface";
3
+ export interface DistributionSecretEntry {
4
+ generated: boolean;
5
+ name: string;
6
+ required: boolean;
7
+ }
8
+ export interface DistributionAgentSummary {
9
+ id: string;
10
+ name: string;
11
+ runtime: string | null;
12
+ teams: string[];
13
+ }
14
+ export interface DistributionTeamSummary {
15
+ agents: string[];
16
+ id: string;
17
+ name: string;
18
+ }
19
+ export interface DistributionOrganizationSummary {
20
+ agents: DistributionAgentSummary[];
21
+ project: string;
22
+ teams: DistributionTeamSummary[];
23
+ }
24
+ export interface DistributionPersistentMount {
25
+ durability: "persistent";
26
+ id: string;
27
+ kind: "volume";
28
+ target: string;
29
+ }
30
+ export interface DistributionWorkspaceResource {
31
+ id: string;
32
+ kind: "git" | "volume";
33
+ link_path: string;
34
+ mode: "mutable" | "readonly";
35
+ mount: string;
36
+ sharing: "per_agent" | "team";
37
+ }
38
+ export interface DistributionRuntimeInstance {
39
+ config_path: string;
40
+ home_path: string | null;
41
+ id: string;
42
+ internal_port: number | null;
43
+ model_auth_methods: Record<string, ModelAuthMethod>;
44
+ model_secrets_required: string[];
45
+ node_ids: string[];
46
+ published_port: number | null;
47
+ runtime: string;
48
+ workspace_path: string;
49
+ }
50
+ export interface DistributionMoltnetNetwork {
51
+ binding: "env";
52
+ id: string;
53
+ server_mode: "external" | "managed";
54
+ }
55
+ export interface DistributionPortMapping {
56
+ internal_port: number;
57
+ published_port: number;
58
+ }
59
+ export interface DistributionReport {
60
+ compile_fingerprint: string;
61
+ generated_at: string;
62
+ internal_ports: number[];
63
+ model_auth_methods: Record<string, ModelAuthMethod>;
64
+ moltnet: {
65
+ networks: DistributionMoltnetNetwork[];
66
+ };
67
+ organization: DistributionOrganizationSummary;
68
+ persistent_mounts: DistributionPersistentMount[];
69
+ port_mappings: DistributionPortMapping[];
70
+ ports: number[];
71
+ resources: DistributionWorkspaceResource[];
72
+ runtime_instances: DistributionRuntimeInstance[];
73
+ secrets: Record<DistributionSecretCategory, DistributionSecretEntry[]>;
74
+ version: "spawnfile.distribution-report.v1";
75
+ }
76
+ export declare const DISTRIBUTION_REPORT_VERSION = "spawnfile.distribution-report.v1";
77
+ export declare const DISTRIBUTION_REPORT_IMAGE_PATH = "/spawnfile/spawnfile-report.json";
78
+ export declare const DISTRIBUTION_REPORT_OUTPUT_FILE = "distribution-report.json";
79
+ export declare const IMAGE_CONTRACT_VERSION = "spawnfile.image.v1";
80
+ export type DistributionImageLabels = Record<"com.spawnfile.compile_fingerprint" | "com.spawnfile.image_contract" | "com.spawnfile.project" | "com.spawnfile.report", string> & Record<string, string>;
@@ -0,0 +1,4 @@
1
+ export const DISTRIBUTION_REPORT_VERSION = "spawnfile.distribution-report.v1";
2
+ export const DISTRIBUTION_REPORT_IMAGE_PATH = "/spawnfile/spawnfile-report.json";
3
+ export const DISTRIBUTION_REPORT_OUTPUT_FILE = "distribution-report.json";
4
+ export const IMAGE_CONTRACT_VERSION = "spawnfile.image.v1";
@@ -0,0 +1,12 @@
1
+ import type { DistributionReport } from "./types.js";
2
+ export interface VerifyDistributionReportInput {
3
+ forbiddenPathFragments?: string[];
4
+ report: unknown;
5
+ }
6
+ /**
7
+ * Pre-push verification for a published image's distribution report. Confirms
8
+ * the report parses, is free of creator host-path fragments, and that every
9
+ * secret entry carries the required/generated markers. Throws on any violation
10
+ * so publishing refuses to ship a leaky or malformed image.
11
+ */
12
+ export declare const verifyDistributionReport: (input: VerifyDistributionReportInput) => DistributionReport;
@@ -0,0 +1,29 @@
1
+ import { parseDistributionReport } from "./distributionReportSchema.js";
2
+ import { SpawnfileError } from "../shared/index.js";
3
+ /**
4
+ * Pre-push verification for a published image's distribution report. Confirms
5
+ * the report parses, is free of creator host-path fragments, and that every
6
+ * secret entry carries the required/generated markers. Throws on any violation
7
+ * so publishing refuses to ship a leaky or malformed image.
8
+ */
9
+ export const verifyDistributionReport = (input) => {
10
+ const report = parseDistributionReport(input.report, "distribution report for publish");
11
+ const serialized = JSON.stringify(report);
12
+ for (const fragment of input.forbiddenPathFragments ?? []) {
13
+ const trimmed = fragment.trim();
14
+ if (trimmed.length > 0 && serialized.includes(trimmed)) {
15
+ throw new SpawnfileError("validation_error", `Distribution report leaks a creator path fragment and must not be published: ${trimmed}`);
16
+ }
17
+ }
18
+ if (serialized.includes("/Users/") || serialized.includes("\\Users\\")) {
19
+ throw new SpawnfileError("validation_error", "Distribution report contains an absolute home path and must not be published");
20
+ }
21
+ for (const category of ["model", "project", "runtime", "surface"]) {
22
+ for (const entry of report.secrets[category]) {
23
+ if (typeof entry.required !== "boolean" || typeof entry.generated !== "boolean") {
24
+ throw new SpawnfileError("validation_error", `Secret ${entry.name} is missing required/generated markers`);
25
+ }
26
+ }
27
+ }
28
+ return report;
29
+ };
@@ -2,5 +2,11 @@ export declare const assertPortableRelativePath: (inputPath: string) => void;
2
2
  export declare const getCanonicalManifestPath: (filePath: string) => string;
3
3
  export declare const getManifestPath: (inputPath: string) => string;
4
4
  export declare const getProjectRoot: (manifestPath: string) => string;
5
+ /**
6
+ * Resolves the output/record directory for a project argument. An explicit
7
+ * `--out` is honored as given; otherwise the default lives under the resolved
8
+ * project root, not the process working directory.
9
+ */
10
+ export declare const resolveProjectOutputDirectory: (inputPath: string, outOption: string | undefined, defaultOutputName: string) => string;
5
11
  export declare const resolveProjectPath: (manifestPath: string, relativePath: string) => string;
6
12
  export declare const toPosixPath: (value: string) => string;
@@ -13,6 +13,17 @@ export const getManifestPath = (inputPath) => path.basename(inputPath) === "Spaw
13
13
  ? path.resolve(inputPath)
14
14
  : path.resolve(inputPath, "Spawnfile");
15
15
  export const getProjectRoot = (manifestPath) => path.dirname(manifestPath);
16
+ /**
17
+ * Resolves the output/record directory for a project argument. An explicit
18
+ * `--out` is honored as given; otherwise the default lives under the resolved
19
+ * project root, not the process working directory.
20
+ */
21
+ export const resolveProjectOutputDirectory = (inputPath, outOption, defaultOutputName) => {
22
+ if (outOption) {
23
+ return path.resolve(outOption);
24
+ }
25
+ return path.join(getProjectRoot(getManifestPath(inputPath)), defaultOutputName);
26
+ };
16
27
  export const resolveProjectPath = (manifestPath, relativePath) => {
17
28
  assertPortableRelativePath(relativePath);
18
29
  const projectRoot = getProjectRoot(manifestPath);
@@ -115,8 +115,18 @@ const validateLocalTeamManifest = async (manifestPath, manifest) => {
115
115
  }
116
116
  };
117
117
  const parseManifest = (source) => {
118
+ let parsed;
118
119
  try {
119
- return manifestSchema.parse(parseYaml(source));
120
+ parsed = parseYaml(source);
121
+ }
122
+ catch (error) {
123
+ // A YAML syntax error is a malformed manifest, not a runtime failure — wrap
124
+ // it so it exits as a usage error instead of leaking the parser's wording.
125
+ const reason = error instanceof Error ? error.message : String(error);
126
+ throw new SpawnfileError("invalid_manifest", `Invalid Spawnfile manifest: ${reason}`);
127
+ }
128
+ try {
129
+ return manifestSchema.parse(parsed);
120
130
  }
121
131
  catch (error) {
122
132
  if (error instanceof z.ZodError) {
@@ -127,7 +137,17 @@ const parseManifest = (source) => {
127
137
  }
128
138
  };
129
139
  export const loadManifest = async (manifestPath) => {
130
- const source = await readUtf8File(manifestPath);
140
+ let source;
141
+ try {
142
+ source = await readUtf8File(manifestPath);
143
+ }
144
+ catch (error) {
145
+ if (error.code === "ENOENT") {
146
+ // A missing Spawnfile is a usage/input error, not a runtime failure.
147
+ throw new SpawnfileError("validation_error", `No Spawnfile found at ${manifestPath}. Pass a project directory or Spawnfile path, or run 'spawnfile init'.`);
148
+ }
149
+ throw error;
150
+ }
131
151
  const manifest = parseManifest(source);
132
152
  if (isAgentManifest(manifest)) {
133
153
  await validateLocalAgentManifest(manifestPath, manifest);
@@ -67,8 +67,7 @@ const orderMoltnetRoomBehavior = (behavior) => {
67
67
  return undefined;
68
68
  }
69
69
  return withDefinedEntries([
70
- ["read", behavior.read],
71
- ["reply", behavior.reply]
70
+ ["wake", behavior.wake]
72
71
  ]);
73
72
  };
74
73
  const orderMoltnetDm = (dms) => {
@@ -77,8 +76,7 @@ const orderMoltnetDm = (dms) => {
77
76
  }
78
77
  return withDefinedEntries([
79
78
  ["enabled", dms.enabled],
80
- ["read", dms.read],
81
- ["reply", dms.reply]
79
+ ["wake", dms.wake]
82
80
  ]);
83
81
  };
84
82
  const orderMoltnetAttachment = (attachment) => withDefinedEntries([