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
@@ -1,5 +1,106 @@
1
+ import { buildDistributionReport, createDistributionImageLabels, DISTRIBUTION_REPORT_OUTPUT_FILE, normalizeProjectLabelSlug } from "../distribution/index.js";
2
+ import { SpawnfileError } from "../shared/index.js";
1
3
  import { createEnvVariableMap, createRuntimeTargetPlans } from "./containerArtifactsPlans.js";
2
4
  import { createRootfsFiles, renderDockerfile, renderEntrypoint, renderEnvExample } from "./containerArtifactsRender.js";
5
+ const createOrganizationSummary = (plan, compiledNodes) => {
6
+ const nodes = compiledNodes.map((node) => ({
7
+ id: node.id ?? `${node.kind}:${node.slug}`,
8
+ kind: node.kind,
9
+ name: node.value.name,
10
+ runtimeName: node.runtimeName,
11
+ source: node.value.source
12
+ }));
13
+ const rootNode = nodes.find((node) => node.source === plan.root)
14
+ ?? nodes.find((node) => !plan.edges.some((edge) => edge.to === node.id))
15
+ ?? nodes[0];
16
+ if (!rootNode) {
17
+ throw new SpawnfileError("compile_error", `Unable to resolve the root node for ${plan.root}`);
18
+ }
19
+ const memberEdges = plan.edges.filter((edge) => edge.kind === "team_member");
20
+ const teamsByAgent = new Map();
21
+ for (const edge of memberEdges) {
22
+ teamsByAgent.set(edge.to, [...(teamsByAgent.get(edge.to) ?? []), edge.from]);
23
+ }
24
+ const agentNodes = nodes.filter((node) => node.kind === "agent");
25
+ const teamNodes = nodes.filter((node) => node.kind === "team");
26
+ const agentIds = new Set(agentNodes.map((node) => node.id));
27
+ return {
28
+ agents: agentNodes
29
+ .map((node) => ({
30
+ id: node.id,
31
+ name: node.name,
32
+ runtime: node.runtimeName,
33
+ teams: [...(teamsByAgent.get(node.id) ?? [])].sort()
34
+ }))
35
+ .sort((left, right) => left.id.localeCompare(right.id)),
36
+ project: rootNode.name,
37
+ teams: teamNodes
38
+ .map((node) => ({
39
+ agents: memberEdges
40
+ .filter((edge) => edge.from === node.id && agentIds.has(edge.to))
41
+ .map((edge) => edge.to)
42
+ .sort(),
43
+ id: node.id,
44
+ name: node.name
45
+ }))
46
+ .sort((left, right) => left.id.localeCompare(right.id))
47
+ };
48
+ };
49
+ const resolveMoltnetOperatorTokenSecret = (plan) => {
50
+ const client = plan.server.auth.client;
51
+ if (!client) {
52
+ return undefined;
53
+ }
54
+ if (client.token_env) {
55
+ return client.token_env;
56
+ }
57
+ if (client.token_path) {
58
+ return client.token_path;
59
+ }
60
+ if (!client.token_id) {
61
+ return undefined;
62
+ }
63
+ return plan.server.auth.tokens?.find((token) => token.id === client.token_id)?.secret;
64
+ };
65
+ const createMoltnetSummary = (moltnet) => {
66
+ if (!moltnet) {
67
+ return undefined;
68
+ }
69
+ return {
70
+ node_plans: moltnet.nodePlans.map((plan) => ({
71
+ config_path: plan.configPath,
72
+ network_id: plan.networkId
73
+ })),
74
+ server_plans: moltnet.serverPlans.map((plan) => {
75
+ const operatorTokenSecret = resolveMoltnetOperatorTokenSecret(plan);
76
+ return {
77
+ auth_mode: plan.server.auth.mode,
78
+ base_url: plan.baseUrl,
79
+ ...(plan.configPath ? { config_path: plan.configPath } : {}),
80
+ ...(plan.server.mode === "managed"
81
+ ? { direct_messages: plan.server.direct_messages }
82
+ : {}),
83
+ id: plan.id,
84
+ mode: plan.mode,
85
+ network_id: plan.networkId,
86
+ ...(operatorTokenSecret ? { operator_token_secret: operatorTokenSecret } : {}),
87
+ ...(plan.port ? { port: plan.port } : {}),
88
+ ...(plan.server.auth.public_read !== undefined
89
+ ? { public_read: plan.server.auth.public_read }
90
+ : {}),
91
+ rooms: plan.rooms.map((room) => ({
92
+ id: room.id,
93
+ members: [...room.members],
94
+ ...(room.visibility ? { visibility: room.visibility } : {}),
95
+ ...(room.write_policy ? { write_policy: room.write_policy } : {})
96
+ })),
97
+ ...(plan.server.mode === "managed"
98
+ ? { store_kind: plan.server.store.kind }
99
+ : {})
100
+ };
101
+ })
102
+ };
103
+ };
3
104
  export const createContainerArtifacts = async (plan, compiledNodes, options = {}) => {
4
105
  const runtimePlans = await createRuntimeTargetPlans(plan, compiledNodes);
5
106
  const envVariables = [...createEnvVariableMap(compiledNodes, runtimePlans).values()].sort((left, right) => left.name.localeCompare(right.name));
@@ -23,11 +124,108 @@ export const createContainerArtifacts = async (plan, compiledNodes, options = {}
23
124
  volume_name: mount.volumeName
24
125
  }))
25
126
  .sort((left, right) => left.id.localeCompare(right.id));
127
+ const runtimeInternalPorts = runtimePlans.flatMap((plan) => plan.port ? [plan.port] : []);
128
+ const internalPorts = [...new Set([...runtimeInternalPorts, ...(options.moltnet?.ports ?? [])])].sort((left, right) => left - right);
129
+ const runtimePublishedPorts = runtimePlans.flatMap((plan) => plan.publishedPort ? [plan.publishedPort] : []);
130
+ const publishedPorts = [
131
+ ...new Set([...runtimePublishedPorts, ...(options.moltnet?.publishedPorts ?? [])])
132
+ ].sort((left, right) => left - right);
133
+ const portMappings = runtimePlans
134
+ .flatMap((plan) => plan.port && plan.publishedPort
135
+ ? [{ internal_port: plan.port, published_port: plan.publishedPort }]
136
+ : [])
137
+ .sort((left, right) => left.published_port - right.published_port
138
+ || left.internal_port - right.internal_port);
139
+ const runtimeHomes = [
140
+ ...new Set(runtimePlans.flatMap((plan) => (plan.instancePaths.homePath ? [plan.instancePaths.homePath] : [])))
141
+ ].sort();
142
+ const runtimeInstances = runtimePlans
143
+ .map((plan) => ({
144
+ config_path: plan.instancePaths.configPath,
145
+ home_path: plan.instancePaths.homePath ?? null,
146
+ id: plan.id,
147
+ internal_port: plan.port ?? null,
148
+ model_auth_methods: plan.modelAuthMethods,
149
+ model_secrets_required: plan.modelSecretsRequired,
150
+ node_ids: [...(plan.sourceIds ?? [])],
151
+ published_port: plan.publishedPort ?? null,
152
+ runtime: plan.runtimeName,
153
+ workspace_path: plan.instancePaths.workspacePath
154
+ }))
155
+ .sort((left, right) => left.id.localeCompare(right.id));
156
+ const runtimesInstalled = [...new Set(runtimePlans.map((plan) => plan.runtimeName))].sort();
157
+ const workspaceResources = [
158
+ ...new Map(runtimePlans.flatMap((plan) => (plan.resources ?? []).map((resource) => [
159
+ `${resource.kind}:${resource.id}:${resource.linkPath}`,
160
+ {
161
+ backing_path: resource.backingPath,
162
+ id: resource.id,
163
+ kind: resource.kind,
164
+ link_path: resource.linkPath,
165
+ mode: resource.mode,
166
+ mount: resource.mount,
167
+ sharing: resource.sharing
168
+ }
169
+ ]))).values()
170
+ ].sort((left, right) => left.link_path.localeCompare(right.link_path) || left.id.localeCompare(right.id));
171
+ const moltnetSummary = createMoltnetSummary(options.moltnet);
172
+ const organization = createOrganizationSummary(plan, compiledNodes);
173
+ const projectSlug = normalizeProjectLabelSlug(organization.project);
174
+ const mergedModelAuthMethods = Object.assign({}, ...runtimePlans.map((runtimePlan) => runtimePlan.modelAuthMethods));
175
+ const moltnetNetworks = [
176
+ ...new Map((options.moltnet?.serverPlans ?? []).map((serverPlan) => [
177
+ serverPlan.networkId,
178
+ {
179
+ binding: "env",
180
+ id: serverPlan.networkId,
181
+ server_mode: serverPlan.mode
182
+ }
183
+ ])).values()
184
+ ];
185
+ const distributionReport = buildDistributionReport({
186
+ envVariables: envVariables.map((variable) => ({
187
+ categories: variable.categories,
188
+ generated: variable.generated,
189
+ name: variable.name,
190
+ required: variable.required
191
+ })),
192
+ generatedAt: options.generatedAt ?? new Date().toISOString(),
193
+ internalPorts,
194
+ modelAuthMethods: mergedModelAuthMethods,
195
+ moltnetNetworks,
196
+ organization,
197
+ persistentMounts: persistentMounts.map((mount) => ({
198
+ durability: "persistent",
199
+ id: mount.id,
200
+ kind: "volume",
201
+ target: mount.mount_path
202
+ })),
203
+ portMappings,
204
+ publishedPorts,
205
+ resources: workspaceResources.map((resource) => ({
206
+ id: resource.id,
207
+ kind: resource.kind,
208
+ link_path: resource.link_path,
209
+ mode: resource.mode,
210
+ mount: resource.mount,
211
+ sharing: resource.sharing
212
+ })),
213
+ runtimeInstances
214
+ });
215
+ const distributionLabels = createDistributionImageLabels(projectSlug, distributionReport.compile_fingerprint);
26
216
  const files = [
27
217
  ...createRootfsFiles(runtimePlans),
28
218
  ...(options.moltnet?.files ?? []),
219
+ {
220
+ content: `${JSON.stringify(distributionReport, null, 2)}\n`,
221
+ path: DISTRIBUTION_REPORT_OUTPUT_FILE
222
+ },
29
223
  {
30
224
  content: await renderDockerfile(runtimePlans, {
225
+ distribution: {
226
+ labels: distributionLabels,
227
+ reportOutputFile: DISTRIBUTION_REPORT_OUTPUT_FILE
228
+ },
31
229
  hasMoltnet: Boolean(options.moltnet),
32
230
  hasStagedMoltnetBinaries: options.hasStagedMoltnetBinaries,
33
231
  moltnetPublishedPorts: options.moltnet?.publishedPorts ?? [],
@@ -51,38 +249,12 @@ export const createContainerArtifacts = async (plan, compiledNodes, options = {}
51
249
  path: ".env.example"
52
250
  }
53
251
  ];
54
- const runtimePorts = runtimePlans.flatMap((plan) => plan.publishedPort ? [plan.publishedPort] : []);
55
- const moltnetPorts = options.moltnet?.publishedPorts ?? [];
56
- const ports = [...new Set([...runtimePorts, ...moltnetPorts])].sort((left, right) => left - right);
57
- const runtimeHomes = [
58
- ...new Set(runtimePlans.flatMap((plan) => (plan.instancePaths.homePath ? [plan.instancePaths.homePath] : [])))
59
- ].sort();
60
- const runtimeInstances = runtimePlans
61
- .map((plan) => ({
62
- config_path: plan.instancePaths.configPath,
63
- home_path: plan.instancePaths.homePath ?? null,
64
- id: plan.id,
65
- model_auth_methods: plan.modelAuthMethods,
66
- model_secrets_required: plan.modelSecretsRequired,
67
- runtime: plan.runtimeName
68
- }))
69
- .sort((left, right) => left.id.localeCompare(right.id));
70
- const runtimesInstalled = [...new Set(runtimePlans.map((plan) => plan.runtimeName))].sort();
71
- const workspaceResources = [
72
- ...new Map(runtimePlans.flatMap((plan) => (plan.resources ?? []).map((resource) => [
73
- `${resource.kind}:${resource.id}:${resource.linkPath}`,
74
- {
75
- backing_path: resource.backingPath,
76
- id: resource.id,
77
- kind: resource.kind,
78
- link_path: resource.linkPath,
79
- mode: resource.mode,
80
- mount: resource.mount,
81
- sharing: resource.sharing
82
- }
83
- ]))).values()
84
- ].sort((left, right) => left.link_path.localeCompare(right.link_path) || left.id.localeCompare(right.id));
85
252
  return {
253
+ distribution: {
254
+ fingerprint: distributionReport.compile_fingerprint,
255
+ labels: distributionLabels,
256
+ report: distributionReport
257
+ },
86
258
  executablePaths: ["entrypoint.sh"],
87
259
  files,
88
260
  ...(options.moltnet
@@ -97,8 +269,12 @@ export const createContainerArtifacts = async (plan, compiledNodes, options = {}
97
269
  dockerfile: "Dockerfile",
98
270
  entrypoint: "entrypoint.sh",
99
271
  env_example: ".env.example",
272
+ internal_ports: internalPorts,
100
273
  model_secrets_required: modelSecretsRequired,
101
- ports,
274
+ ...(moltnetSummary ? { moltnet: moltnetSummary } : {}),
275
+ port_mappings: portMappings,
276
+ ports: publishedPorts,
277
+ published_ports: publishedPorts,
102
278
  runtime_instances: runtimeInstances,
103
279
  runtime_homes: runtimeHomes,
104
280
  runtime_secrets_required: runtimeSecretsRequired,
@@ -65,12 +65,13 @@ const resolveInstancePaths = (runtimeName, targetId, meta) => {
65
65
  };
66
66
  export const createEnvVariableMap = (compiledNodes, runtimePlans) => {
67
67
  const variables = new Map();
68
- const register = (name, required, description, category) => {
68
+ const register = (name, required, description, category, generated = false) => {
69
69
  const current = variables.get(name);
70
70
  if (!current) {
71
71
  variables.set(name, {
72
72
  categories: [category],
73
73
  description,
74
+ generated,
74
75
  name,
75
76
  required
76
77
  });
@@ -79,6 +80,7 @@ export const createEnvVariableMap = (compiledNodes, runtimePlans) => {
79
80
  variables.set(name, {
80
81
  ...current,
81
82
  categories: [...new Set([...current.categories, category])],
83
+ generated: current.generated && generated,
82
84
  required: current.required || required
83
85
  });
84
86
  };
@@ -104,10 +106,10 @@ export const createEnvVariableMap = (compiledNodes, runtimePlans) => {
104
106
  }
105
107
  for (const runtimePlan of runtimePlans) {
106
108
  for (const variable of runtimePlan.meta.env ?? []) {
107
- register(variable.name, variable.required, variable.description, "runtime");
109
+ register(variable.name, variable.required, variable.description, "runtime", variable.generated ?? false);
108
110
  }
109
111
  for (const binding of runtimePlan.targetConfigEnvBindings ?? []) {
110
- register(binding.envName, true, `Injected into ${runtimePlan.runtimeName} runtime config`, "runtime");
112
+ register(binding.envName, true, `Injected into ${runtimePlan.runtimeName} runtime config`, "runtime", binding.generated ?? false);
111
113
  }
112
114
  }
113
115
  return variables;
@@ -165,7 +167,7 @@ export const createRuntimeTargetPlans = async (plan, compiledNodes) => {
165
167
  .filter((node) => node.runtimeName === runtimeName && node.emittedFiles.length > 0)
166
168
  .map((node) => ({
167
169
  emittedFiles: node.emittedFiles,
168
- id: `${node.kind}:${node.slug}`,
170
+ id: node.id ?? `${node.kind}:${node.slug}`,
169
171
  kind: node.kind,
170
172
  slug: node.slug,
171
173
  value: node.value
@@ -192,6 +194,7 @@ export const createRuntimeTargetPlans = async (plan, compiledNodes) => {
192
194
  resources: resolveTargetResources(target, targetInputs, instancePaths, adapter.container),
193
195
  runtimeName,
194
196
  runtimeRoot: recipe.runtimeRoot,
197
+ sourceIds: [...(target.sourceIds ?? [])].sort(),
195
198
  targetConfigEnvBindings: target.configEnvBindings,
196
199
  targetFiles: target.files
197
200
  });
@@ -4,5 +4,11 @@ import type { EntrypointOptions } from "./containerEntrypointRender.js";
4
4
  export { renderEntrypoint } from "./containerEntrypointRender.js";
5
5
  export type { EntrypointOptions } from "./containerEntrypointRender.js";
6
6
  export declare const renderEnvExample: (variables: ContainerEnvVariable[]) => string;
7
- export declare const renderDockerfile: (runtimePlans: RuntimeTargetPlan[], options?: EntrypointOptions) => Promise<string>;
7
+ export interface DockerfileDistributionOptions {
8
+ labels: Record<string, string>;
9
+ reportOutputFile: string;
10
+ }
11
+ export declare const renderDockerfile: (runtimePlans: RuntimeTargetPlan[], options?: EntrypointOptions & {
12
+ distribution?: DockerfileDistributionOptions;
13
+ }) => Promise<string>;
8
14
  export declare const createRootfsFiles: (runtimePlans: RuntimeTargetPlan[]) => EmittedFile[];
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { DISTRIBUTION_REPORT_IMAGE_PATH } from "../distribution/index.js";
2
3
  import { createRuntimeInstallRecipe } from "../runtime/index.js";
3
4
  import { SpawnfileError } from "../shared/index.js";
4
5
  export { renderEntrypoint } from "./containerEntrypointRender.js";
@@ -194,6 +195,11 @@ export const renderDockerfile = async (runtimePlans, options = {}) => {
194
195
  }
195
196
  lines.push('RUN if ! id -u spawnfile >/dev/null 2>&1; then useradd --create-home --home-dir /home/spawnfile --shell /bin/bash spawnfile; fi', "");
196
197
  lines.push("COPY container/rootfs/ /", "COPY .env.example /opt/spawnfile/.env.example", 'COPY entrypoint.sh /opt/spawnfile/entrypoint.sh', "RUN chmod +x /opt/spawnfile/entrypoint.sh");
198
+ if (options.distribution) {
199
+ lines.push("", `COPY ${options.distribution.reportOutputFile} ${DISTRIBUTION_REPORT_IMAGE_PATH}`, ...Object.entries(options.distribution.labels)
200
+ .sort(([left], [right]) => left.localeCompare(right))
201
+ .map(([name, value]) => `LABEL ${name}=${shellQuote(value)}`));
202
+ }
197
203
  lines.push(`RUN ${createStateOwnershipCommand(options.persistentMountPaths)}`);
198
204
  if (exposedPorts.length > 0) {
199
205
  lines.push(`EXPOSE ${exposedPorts.join(" ")}`);
@@ -1,3 +1,4 @@
1
+ import type { DistributionReport } from "../distribution/index.js";
1
2
  import type { ContainerReport } from "../report/index.js";
2
3
  import type { EmittedFile, RuntimeContainerConfigEnvBinding, RuntimeContainerMeta } from "../runtime/index.js";
3
4
  import type { ModelAuthMethod } from "../shared/index.js";
@@ -7,6 +8,7 @@ import type { WorkspaceResourcePlan } from "./workspaceResources.js";
7
8
  export interface ContainerEnvVariable {
8
9
  categories: Array<"model" | "project" | "runtime" | "surface">;
9
10
  description: string;
11
+ generated: boolean;
10
12
  name: string;
11
13
  required: boolean;
12
14
  }
@@ -32,17 +34,24 @@ export interface RuntimeTargetPlan {
32
34
  resources?: WorkspaceResourcePlan[];
33
35
  runtimeName: string;
34
36
  runtimeRoot: string;
37
+ sourceIds?: string[];
35
38
  targetConfigEnvBindings?: RuntimeContainerConfigEnvBinding[];
36
39
  targetFiles: EmittedFile[];
37
40
  }
38
41
  export interface CompiledNodeArtifact {
39
42
  emittedFiles: EmittedFile[];
43
+ id?: string;
40
44
  kind: "agent" | "team";
41
45
  runtimeName: string | null;
42
46
  slug: string;
43
47
  value: ResolvedAgentNode | ResolvedTeamNode;
44
48
  }
45
49
  export interface GeneratedContainerArtifacts {
50
+ distribution: {
51
+ fingerprint: string;
52
+ labels: Record<string, string>;
53
+ report: DistributionReport;
54
+ };
46
55
  executablePaths: string[];
47
56
  files: EmittedFile[];
48
57
  moltnet?: {
@@ -1,5 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { resolveMoltnetStorePath } from "./moltnetConfigLowering.js";
3
+ import { networkUrlEnvName } from "./networkBinding.js";
3
4
  import { createWorkspaceResourceCommands, createWorkspaceResourceShellFunctions } from "./containerWorkspaceResourceRender.js";
4
5
  const MOLTNET_SERVER_DATA_DIRECTORY = "/var/lib/spawnfile/moltnet/servers";
5
6
  const shellQuote = (value) => `'${value.replace(/'/g, `'\"'\"'`)}'`;
@@ -8,9 +9,9 @@ const createEnvironmentAssignments = (plan) => {
8
9
  if (plan.instancePaths.homePath) {
9
10
  envAssignments.push(`HOME=${shellQuote(plan.instancePaths.homePath)}`);
10
11
  }
11
- if (plan.instancePaths.homePath && ((plan.runtimeName === "tinyclaw" &&
12
- (plan.modelAuthMethods.openai === "api_key" || plan.modelAuthMethods.openai === "codex")) ||
13
- (plan.runtimeName === "picoclaw" && plan.modelAuthMethods.openai === "codex"))) {
12
+ if (plan.instancePaths.homePath &&
13
+ plan.runtimeName === "picoclaw" &&
14
+ plan.modelAuthMethods.openai === "codex") {
14
15
  envAssignments.push(`CODEX_HOME=${shellQuote(path.posix.join(plan.instancePaths.homePath, ".codex"))}`);
15
16
  }
16
17
  if (plan.meta.homeEnv && plan.instancePaths.homePath) {
@@ -29,14 +30,6 @@ const createEnvironmentAssignments = (plan) => {
29
30
  };
30
31
  const createEnvFileWrites = (plan) => plan.envFiles.map((binding) => `write_env_file ${shellQuote(binding.envName)} ${shellQuote(binding.filePath)}`);
31
32
  const createConfigEnvWrites = (plan) => (plan.configEnvBindings ?? []).map((binding) => `apply_json_env_value ${shellQuote(plan.instancePaths.configPath)} ${shellQuote(binding.envName)} ${shellQuote(binding.jsonPath)}`);
32
- const createAuthSetupCommands = (plan) => {
33
- if (plan.runtimeName !== "tinyclaw" ||
34
- !plan.instancePaths.homePath ||
35
- plan.modelAuthMethods.openai !== "api_key") {
36
- return [];
37
- }
38
- return [`configure_codex_api_key_auth ${shellQuote(plan.instancePaths.homePath)}`];
39
- };
40
33
  const createMoltnetStorePrepareCommands = (serverPlan) => {
41
34
  const server = serverPlan.server;
42
35
  if (server.mode !== "managed" || server.store.kind === "memory" || server.store.kind === "postgres") {
@@ -145,15 +138,6 @@ export const renderEntrypoint = (runtimePlans, requiredSecrets, options = {}) =>
145
138
  "PY",
146
139
  "}",
147
140
  "",
148
- "configure_codex_api_key_auth() {",
149
- ' local home_path="$1"',
150
- ' if [ -z "${OPENAI_API_KEY:-}" ]; then',
151
- " return",
152
- " fi",
153
- ' mkdir -p "$home_path/.codex"',
154
- ' printf "%s\\n" "${OPENAI_API_KEY:-}" | HOME="$home_path" CODEX_HOME="$home_path/.codex" codex login --with-api-key >/dev/null',
155
- "}",
156
- "",
157
141
  ...createWorkspaceResourceShellFunctions()
158
142
  ];
159
143
  lines.push('if [ -n "${OPENCLAW_GATEWAY_TOKEN:-}" ] && [ -z "${OPENCLAW_HOOKS_TOKEN:-}" ]; then', ' export OPENCLAW_HOOKS_TOKEN="hooks-${OPENCLAW_GATEWAY_TOKEN}"', "fi", "");
@@ -171,7 +155,7 @@ export const renderEntrypoint = (runtimePlans, requiredSecrets, options = {}) =>
171
155
  const plan = runtimePlans[0];
172
156
  const commandTokens = resolveStartCommand(plan);
173
157
  const envAssignments = createEnvironmentAssignments(plan);
174
- lines.push(`mkdir -p ${shellQuote(plan.instancePaths.workspacePath)}`, ...createWorkspaceResourceCommands(plan), `require_file ${shellQuote(plan.instancePaths.configPath)}`, ...createEnvFileWrites(plan), ...createConfigEnvWrites(plan), ...createAuthSetupCommands(plan), `${envAssignments.join(" ")} exec ${commandTokens.map(shellQuote).join(" ")}`);
158
+ lines.push(`mkdir -p ${shellQuote(plan.instancePaths.workspacePath)}`, ...createWorkspaceResourceCommands(plan), `require_file ${shellQuote(plan.instancePaths.configPath)}`, ...createEnvFileWrites(plan), ...createConfigEnvWrites(plan), `${envAssignments.join(" ")} exec ${commandTokens.map(shellQuote).join(" ")}`);
175
159
  return `${lines.join("\n").trimEnd()}\n`;
176
160
  }
177
161
  lines.push("PIDS=()", "", "terminate_children() {", ' for pid in "${PIDS[@]:-}"; do', ' kill "$pid" 2>/dev/null || true', " done", "}", "", "trap terminate_children INT TERM EXIT", "");
@@ -183,25 +167,28 @@ export const renderEntrypoint = (runtimePlans, requiredSecrets, options = {}) =>
183
167
  if (!serverPlan.configPath) {
184
168
  continue;
185
169
  }
170
+ const urlEnv = networkUrlEnvName(serverPlan.networkId);
171
+ const serverLines = [];
186
172
  for (const patch of serverPlan.secretPatches) {
187
- lines.push(`apply_json_env_value ${shellQuote(serverPlan.configPath)} ${shellQuote(patch.envName)} ${shellQuote(patch.jsonPath)}`);
173
+ serverLines.push(`apply_json_env_value ${shellQuote(serverPlan.configPath)} ${shellQuote(patch.envName)} ${shellQuote(patch.jsonPath)}`);
188
174
  }
189
- lines.push(...createMoltnetStorePrepareCommands(serverPlan), `MOLTNET_CONFIG=${shellQuote(serverPlan.configPath)} /usr/local/bin/moltnet &`, 'PIDS+=("$!")', "");
190
- }
191
- for (const serverPlan of managedMoltnetServerPlans) {
192
- if (!serverPlan.port) {
193
- continue;
175
+ serverLines.push(...createMoltnetStorePrepareCommands(serverPlan), `MOLTNET_CONFIG=${shellQuote(serverPlan.configPath)} /usr/local/bin/moltnet &`, 'PIDS+=("$!")');
176
+ if (serverPlan.port) {
177
+ serverLines.push(`until curl -sf ${shellQuote(`http://127.0.0.1:${serverPlan.port}/healthz`)} >/dev/null; do sleep 1; done`);
194
178
  }
195
- lines.push(`until curl -sf ${shellQuote(`http://127.0.0.1:${serverPlan.port}/healthz`)} >/dev/null; do sleep 1; done`);
196
- lines.push("");
179
+ // Suppress the in-image managed server when an external endpoint is bound.
180
+ lines.push(`if [ -z "\${${urlEnv}:-}" ]; then`, ...serverLines.map((line) => ` ${line}`), "fi", "");
197
181
  }
198
182
  for (const plan of runtimePlans) {
199
183
  const commandTokens = resolveStartCommand(plan);
200
184
  const envAssignments = createEnvironmentAssignments(plan);
201
- lines.push(`mkdir -p ${shellQuote(plan.instancePaths.workspacePath)}`, ...createWorkspaceResourceCommands(plan), `require_file ${shellQuote(plan.instancePaths.configPath)}`, ...createEnvFileWrites(plan), ...createConfigEnvWrites(plan), ...createAuthSetupCommands(plan), `${envAssignments.join(" ")} ${commandTokens.map(shellQuote).join(" ")} &`, 'PIDS+=("$!")', "", ...createRuntimeReadinessWait(plan));
185
+ lines.push(`mkdir -p ${shellQuote(plan.instancePaths.workspacePath)}`, ...createWorkspaceResourceCommands(plan), `require_file ${shellQuote(plan.instancePaths.configPath)}`, ...createEnvFileWrites(plan), ...createConfigEnvWrites(plan), `${envAssignments.join(" ")} ${commandTokens.map(shellQuote).join(" ")} &`, 'PIDS+=("$!")', "", ...createRuntimeReadinessWait(plan));
202
186
  }
203
187
  for (const nodePlan of moltnetNodePlans) {
204
- lines.push(`/usr/local/bin/moltnet node ${shellQuote(nodePlan.configPath)} &`, 'PIDS+=("$!")', "");
188
+ const urlEnv = networkUrlEnvName(nodePlan.networkId);
189
+ lines.push(
190
+ // Rebind the bridge endpoint when an external network URL is provided.
191
+ `if [ -n "\${${urlEnv}:-}" ]; then`, ` apply_json_env_value ${shellQuote(nodePlan.configPath)} ${shellQuote(urlEnv)} ${shellQuote("moltnet.base_url")}`, "fi", `/usr/local/bin/moltnet node ${shellQuote(nodePlan.configPath)} &`, 'PIDS+=("$!")', "");
205
192
  }
206
193
  lines.push('if [ "${#PIDS[@]}" -eq 0 ]; then', ' echo "No runtime targets were generated for this compile output" >&2', " exit 1", "fi", "", "status=0", 'for pid in "${PIDS[@]}"; do', ' if ! wait "$pid"; then', " status=1", " fi", "done", "", 'exit "$status"');
207
194
  return `${lines.join("\n").trimEnd()}\n`;
@@ -1,6 +1,7 @@
1
1
  export * from "./addProjectNode.js";
2
2
  export * from "./buildCompilePlan.js";
3
3
  export * from "./buildProject.js";
4
+ export * from "./publishProject.js";
4
5
  export * from "./upProject.js";
5
6
  export * from "./compileProject.js";
6
7
  export * from "./initProject.js";
@@ -1,6 +1,7 @@
1
1
  export * from "./addProjectNode.js";
2
2
  export * from "./buildCompilePlan.js";
3
3
  export * from "./buildProject.js";
4
+ export * from "./publishProject.js";
4
5
  export * from "./upProject.js";
5
6
  export * from "./compileProject.js";
6
7
  export * from "./initProject.js";
@@ -14,6 +14,8 @@ export interface MoltnetServerPlan {
14
14
  id: string;
15
15
  members: string[];
16
16
  name?: string;
17
+ visibility?: "public" | "private";
18
+ write_policy?: "members" | "operators" | "registered_agents";
17
19
  }>;
18
20
  server: TeamNetworkServer;
19
21
  secretPatches: MoltnetSecretPatch[];