spawnfile 0.1.11 → 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 (182) 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/compileProject.js +19 -10
  14. package/dist/compiler/containerArtifacts.d.ts +1 -0
  15. package/dist/compiler/containerArtifacts.js +208 -32
  16. package/dist/compiler/containerArtifactsPlans.js +7 -4
  17. package/dist/compiler/containerArtifactsRender.d.ts +7 -1
  18. package/dist/compiler/containerArtifactsRender.js +6 -0
  19. package/dist/compiler/containerArtifactsTypes.d.ts +9 -0
  20. package/dist/compiler/containerEntrypointRender.js +18 -31
  21. package/dist/compiler/index.d.ts +1 -0
  22. package/dist/compiler/index.js +1 -0
  23. package/dist/compiler/moltnetArtifacts.js +5 -0
  24. package/dist/compiler/moltnetClientConfig.d.ts +1 -1
  25. package/dist/compiler/moltnetClientConfig.js +3 -16
  26. package/dist/compiler/moltnetNodeConfig.js +2 -4
  27. package/dist/compiler/moltnetResolution.js +1 -1
  28. package/dist/compiler/moltnetRoomMemberships.js +1 -2
  29. package/dist/compiler/moltnetRuntimeConfig.d.ts +1 -1
  30. package/dist/compiler/moltnetRuntimeConfig.js +1 -13
  31. package/dist/compiler/networkBinding.d.ts +14 -0
  32. package/dist/compiler/networkBinding.js +34 -0
  33. package/dist/compiler/publishProject.d.ts +13 -0
  34. package/dist/compiler/publishProject.js +72 -0
  35. package/dist/compiler/runProject.d.ts +26 -12
  36. package/dist/compiler/runProject.js +102 -125
  37. package/dist/compiler/runProjectAuth.d.ts +5 -0
  38. package/dist/compiler/runProjectAuth.js +98 -0
  39. package/dist/compiler/runProjectDocker.d.ts +19 -0
  40. package/dist/compiler/runProjectDocker.js +57 -0
  41. package/dist/compiler/types.d.ts +1 -2
  42. package/dist/compiler/upProject.d.ts +6 -0
  43. package/dist/compiler/upProject.js +42 -8
  44. package/dist/compiler/view/buildOrganizationView.js +4 -1
  45. package/dist/compiler/view/declarations.d.ts +4 -0
  46. package/dist/compiler/view/declarations.js +104 -0
  47. package/dist/compiler/view/renderNetworks.js +1 -2
  48. package/dist/compiler/view/renderTree.js +6 -2
  49. package/dist/compiler/view/types.d.ts +64 -1
  50. package/dist/deployment/dockerInspect.d.ts +29 -0
  51. package/dist/deployment/dockerInspect.js +130 -0
  52. package/dist/deployment/dockerLabels.d.ts +16 -0
  53. package/dist/deployment/dockerLabels.js +32 -0
  54. package/dist/deployment/dockerLogs.d.ts +24 -0
  55. package/dist/deployment/dockerLogs.js +96 -0
  56. package/dist/deployment/dockerManager.d.ts +59 -0
  57. package/dist/deployment/dockerManager.js +76 -0
  58. package/dist/deployment/dockerProbeGateway.d.ts +16 -0
  59. package/dist/deployment/dockerProbeGateway.js +48 -0
  60. package/dist/deployment/homeStore.d.ts +19 -0
  61. package/dist/deployment/homeStore.js +130 -0
  62. package/dist/deployment/index.d.ts +9 -0
  63. package/dist/deployment/index.js +9 -0
  64. package/dist/deployment/names.d.ts +3 -0
  65. package/dist/deployment/names.js +12 -0
  66. package/dist/deployment/record.d.ts +76 -0
  67. package/dist/deployment/record.js +174 -0
  68. package/dist/deployment/target.d.ts +35 -0
  69. package/dist/deployment/target.js +96 -0
  70. package/dist/distribution/buildDistributionReport.d.ts +22 -0
  71. package/dist/distribution/buildDistributionReport.js +51 -0
  72. package/dist/distribution/consumeImage.d.ts +29 -0
  73. package/dist/distribution/consumeImage.js +254 -0
  74. package/dist/distribution/consumeImageSupport.d.ts +18 -0
  75. package/dist/distribution/consumeImageSupport.js +63 -0
  76. package/dist/distribution/distributionReportSchema.d.ts +84 -0
  77. package/dist/distribution/distributionReportSchema.js +107 -0
  78. package/dist/distribution/dockerRunner.d.ts +5 -0
  79. package/dist/distribution/dockerRunner.js +21 -0
  80. package/dist/distribution/extractImage.d.ts +25 -0
  81. package/dist/distribution/extractImage.js +96 -0
  82. package/dist/distribution/fingerprint.d.ts +2 -0
  83. package/dist/distribution/fingerprint.js +14 -0
  84. package/dist/distribution/imageRef.d.ts +9 -0
  85. package/dist/distribution/imageRef.js +65 -0
  86. package/dist/distribution/imageRuntimeAuth.d.ts +19 -0
  87. package/dist/distribution/imageRuntimeAuth.js +71 -0
  88. package/dist/distribution/index.d.ts +24 -0
  89. package/dist/distribution/index.js +16 -0
  90. package/dist/distribution/preflight.d.ts +11 -0
  91. package/dist/distribution/preflight.js +65 -0
  92. package/dist/distribution/projectImageView.d.ts +8 -0
  93. package/dist/distribution/projectImageView.js +83 -0
  94. package/dist/distribution/projectName.d.ts +1 -0
  95. package/dist/distribution/projectName.js +12 -0
  96. package/dist/distribution/renderImageInterface.d.ts +30 -0
  97. package/dist/distribution/renderImageInterface.js +86 -0
  98. package/dist/distribution/tarReader.d.ts +10 -0
  99. package/dist/distribution/tarReader.js +69 -0
  100. package/dist/distribution/types.d.ts +80 -0
  101. package/dist/distribution/types.js +4 -0
  102. package/dist/distribution/verifyDistributionReport.d.ts +12 -0
  103. package/dist/distribution/verifyDistributionReport.js +29 -0
  104. package/dist/filesystem/paths.d.ts +6 -0
  105. package/dist/filesystem/paths.js +11 -0
  106. package/dist/manifest/loadManifest.js +22 -2
  107. package/dist/manifest/renderSpawnfile.js +2 -4
  108. package/dist/manifest/schemas.d.ts +17 -41
  109. package/dist/manifest/surfaceSchemas.d.ts +19 -47
  110. package/dist/manifest/surfaceSchemas.js +3 -6
  111. package/dist/report/createReport.d.ts +7 -1
  112. package/dist/report/createReport.js +28 -1
  113. package/dist/report/types.d.ts +43 -0
  114. package/dist/runtime/container.js +0 -16
  115. package/dist/runtime/index.d.ts +1 -0
  116. package/dist/runtime/index.js +1 -0
  117. package/dist/runtime/install.d.ts +0 -10
  118. package/dist/runtime/install.js +0 -45
  119. package/dist/runtime/openclaw/adapter.js +9 -1
  120. package/dist/runtime/openclaw/configAuth.d.ts +15 -0
  121. package/dist/runtime/openclaw/configAuth.js +46 -0
  122. package/dist/runtime/openclaw/runAuth.d.ts +6 -0
  123. package/dist/runtime/openclaw/runAuth.js +7 -55
  124. package/dist/runtime/openclaw/statusProbes.d.ts +2 -0
  125. package/dist/runtime/openclaw/statusProbes.js +31 -0
  126. package/dist/runtime/picoclaw/adapter.js +10 -0
  127. package/dist/runtime/picoclaw/runAuth.d.ts +7 -0
  128. package/dist/runtime/picoclaw/runAuth.js +8 -68
  129. package/dist/runtime/picoclaw/statusProbes.d.ts +2 -0
  130. package/dist/runtime/picoclaw/statusProbes.js +100 -0
  131. package/dist/runtime/registry.d.ts +0 -5
  132. package/dist/runtime/registry.js +2 -12
  133. package/dist/runtime/statusProbes.d.ts +16 -0
  134. package/dist/runtime/statusProbes.js +41 -0
  135. package/dist/runtime/types.d.ts +52 -1
  136. package/dist/shared/errors.d.ts +6 -0
  137. package/dist/shared/errors.js +9 -0
  138. package/dist/status/buildStatus.d.ts +13 -0
  139. package/dist/status/buildStatus.js +266 -0
  140. package/dist/status/compileReport.d.ts +96 -0
  141. package/dist/status/compileReport.js +230 -0
  142. package/dist/status/compiledContainerObservations.d.ts +5 -0
  143. package/dist/status/compiledContainerObservations.js +63 -0
  144. package/dist/status/deploymentLogs.d.ts +12 -0
  145. package/dist/status/deploymentLogs.js +45 -0
  146. package/dist/status/deployments.d.ts +14 -0
  147. package/dist/status/deployments.js +128 -0
  148. package/dist/status/imageStatusReport.d.ts +10 -0
  149. package/dist/status/imageStatusReport.js +73 -0
  150. package/dist/status/index.d.ts +14 -0
  151. package/dist/status/index.js +14 -0
  152. package/dist/status/moltnetProbes.d.ts +21 -0
  153. package/dist/status/moltnetProbes.js +240 -0
  154. package/dist/status/registryDrift.d.ts +28 -0
  155. package/dist/status/registryDrift.js +119 -0
  156. package/dist/status/renderStatus.d.ts +2 -0
  157. package/dist/status/renderStatus.js +230 -0
  158. package/dist/status/runtimeProbes.d.ts +13 -0
  159. package/dist/status/runtimeProbes.js +111 -0
  160. package/dist/status/selectionSubjects.d.ts +8 -0
  161. package/dist/status/selectionSubjects.js +77 -0
  162. package/dist/status/selectors.d.ts +14 -0
  163. package/dist/status/selectors.js +90 -0
  164. package/dist/status/traversal.d.ts +6 -0
  165. package/dist/status/traversal.js +15 -0
  166. package/dist/status/types.d.ts +113 -0
  167. package/dist/status/types.js +1 -0
  168. package/package.json +4 -2
  169. package/runtimes.yaml +22 -15
  170. package/dist/runtime/tinyclaw/adapter.d.ts +0 -2
  171. package/dist/runtime/tinyclaw/adapter.js +0 -284
  172. package/dist/runtime/tinyclaw/runAuth.d.ts +0 -2
  173. package/dist/runtime/tinyclaw/runAuth.js +0 -49
  174. package/dist/runtime/tinyclaw/scaffold-assets/AGENTS.md +0 -160
  175. package/dist/runtime/tinyclaw/scaffold-assets/CLAUDE.md +0 -5
  176. package/dist/runtime/tinyclaw/scaffold-assets/SOUL.md +0 -177
  177. package/dist/runtime/tinyclaw/scaffold.d.ts +0 -2
  178. package/dist/runtime/tinyclaw/scaffold.js +0 -24
  179. package/dist/runtime/tinyclaw/schedules.d.ts +0 -9
  180. package/dist/runtime/tinyclaw/schedules.js +0 -62
  181. package/dist/runtime/tinyclaw/surfaces.d.ts +0 -8
  182. package/dist/runtime/tinyclaw/surfaces.js +0 -94
@@ -0,0 +1,394 @@
1
+ import { inspectDockerDeployment, listDeploymentRecords, listHomeDeploymentRecords, readHomeDeploymentRecord, readHomeDeploymentReport } from "../deployment/index.js";
2
+ import { extractImageReport, parseDistributionReport, projectImageOrganizationView, renderImageInterface } from "../distribution/index.js";
3
+ import { collectRegistryDriftObservations } from "../status/index.js";
4
+ import { resolveProjectOutputDirectory } from "../filesystem/index.js";
5
+ import { DEFAULT_OUTPUT_DIRECTORY, errorExitCode, SpawnfileError } from "../shared/index.js";
6
+ import { resolveCommandInput } from "./resolveCommandInput.js";
7
+ import { loadedImageCompileReport } from "../status/index.js";
8
+ import { createStaticStatus, createDeploymentSummaries, collectDeploymentLogObservations, collectMoltnetProbeObservations, collectRuntimeProbeObservations, exitCodeForStatus, loadCompileReport, renderStatus, resolveStatusSelector } from "../status/index.js";
9
+ const inputFailure = (message) => ({
10
+ error: message,
11
+ exitCode: 2
12
+ });
13
+ const resolveOutputMode = (options) => {
14
+ const modes = [
15
+ ...(options.json ? ["json"] : []),
16
+ ...(options.pretty ? ["pretty"] : []),
17
+ ...(options.quiet ? ["quiet"] : [])
18
+ ];
19
+ if (modes.length > 1) {
20
+ return inputFailure("Choose only one status output mode: --pretty, --json, or --quiet");
21
+ }
22
+ return modes[0] ?? "pretty";
23
+ };
24
+ const resolveSelectorInput = (options) => {
25
+ const selectors = [
26
+ ...(options.agent ? [{ kind: "agent", value: options.agent }] : []),
27
+ ...(options.team ? [{ kind: "team", value: options.team }] : []),
28
+ ...(options.network ? [{ kind: "network", value: options.network }] : []),
29
+ ...(options.runtime ? [{ kind: "runtime", value: options.runtime }] : [])
30
+ ];
31
+ if (selectors.length > 1) {
32
+ return inputFailure("Choose only one status selector: --agent, --team, --network, or --runtime");
33
+ }
34
+ return selectors[0] ?? null;
35
+ };
36
+ const resolveTimeoutMs = (options) => {
37
+ if (!options.timeout) {
38
+ return undefined;
39
+ }
40
+ const parsed = Number(options.timeout);
41
+ if (!Number.isInteger(parsed) || parsed <= 0) {
42
+ return inputFailure("status --timeout must be a positive integer number of milliseconds");
43
+ }
44
+ return parsed;
45
+ };
46
+ const resolveDeploymentRecords = (records, options) => {
47
+ if (options.deployment) {
48
+ const record = records.find((entry) => entry.record.name === options.deployment);
49
+ return record
50
+ ? [record]
51
+ : inputFailure(`Unknown deployment "${options.deployment}". Valid deployments: ${records.map((entry) => entry.record.name).sort().join(", ") || "none"}`);
52
+ }
53
+ if (options.live && records.length > 1) {
54
+ return inputFailure(`status --live requires --deployment when multiple records exist: ${records.map((entry) => entry.record.name).sort().join(", ")}`);
55
+ }
56
+ return records;
57
+ };
58
+ const inspectDeployments = async (records, handlers, options, timeoutMs) => {
59
+ if (!options.live || options.recover) {
60
+ return new Map();
61
+ }
62
+ const inspect = handlers.inspectDockerDeployment ?? inspectDockerDeployment;
63
+ const inspections = await Promise.all(records.map(async ({ record }) => [
64
+ record.name,
65
+ await inspect(record, { timeoutMs })
66
+ ]));
67
+ return new Map(inspections);
68
+ };
69
+ const resolveAuthValues = async (records, handlers) => {
70
+ const values = {};
71
+ if (!handlers.requireAuthProfile) {
72
+ return values;
73
+ }
74
+ const profileNames = [...new Set(records
75
+ .map(({ record }) => record.auth_profile)
76
+ .filter((profileName) => typeof profileName === "string" && profileName.length > 0))];
77
+ for (const profileName of profileNames) {
78
+ try {
79
+ const profile = await handlers.requireAuthProfile(profileName);
80
+ Object.assign(values, profile.env);
81
+ }
82
+ catch {
83
+ // Missing profile values are reported by the metadata layer as unknown credentials.
84
+ }
85
+ }
86
+ return values;
87
+ };
88
+ const emitOutput = (streams, output) => {
89
+ for (const line of output.split("\n")) {
90
+ streams.stdout(line);
91
+ }
92
+ };
93
+ const wait = (ms) => new Promise((resolve) => {
94
+ setTimeout(resolve, ms);
95
+ });
96
+ export const executeStatusWatch = async (inputPath, options, handlers, streams, setExitCode, watchOptions = {}) => {
97
+ const intervalMs = watchOptions.intervalMs ?? 5_000;
98
+ const sleep = watchOptions.sleep ?? wait;
99
+ let iteration = 0;
100
+ while (watchOptions.iterations === undefined || iteration < watchOptions.iterations) {
101
+ if (iteration > 0) {
102
+ await sleep(intervalMs);
103
+ streams.stdout("");
104
+ }
105
+ const result = await executeStatusCommand(inputPath, options, handlers);
106
+ setExitCode(result.exitCode);
107
+ if (result.error) {
108
+ streams.stderr(`error: ${result.error}`);
109
+ return;
110
+ }
111
+ if (result.output) {
112
+ emitOutput(streams, result.output);
113
+ }
114
+ iteration += 1;
115
+ }
116
+ };
117
+ /* v8 ignore start -- docker extraction is covered by distribution E2E */
118
+ const runStaticImageStatus = async (imageRef, options, json) => {
119
+ try {
120
+ const inspection = await extractImageReport(imageRef, {
121
+ dockerCommand: options.dockerCommand,
122
+ dockerContext: options.context,
123
+ pull: options.pull
124
+ });
125
+ return {
126
+ exitCode: 0,
127
+ output: renderImageInterface(inspection.report, { imageRef, json })
128
+ };
129
+ }
130
+ catch (error) {
131
+ // Input errors (bad labels, non-JSON report, fingerprint mismatch) exit 2;
132
+ // Docker/runtime failures (daemon down, pull/create failure) exit 1.
133
+ return {
134
+ error: error instanceof Error ? error.message : String(error),
135
+ exitCode: errorExitCode(error)
136
+ };
137
+ }
138
+ };
139
+ /* v8 ignore stop */
140
+ const runHomeDeploymentStatus = async (options, handlers, mode, timeoutMs) => {
141
+ let records;
142
+ try {
143
+ records = await listHomeDeploymentRecords();
144
+ }
145
+ catch (error) {
146
+ return { error: error instanceof Error ? error.message : String(error), exitCode: errorExitCode(error) };
147
+ }
148
+ if (!options.deployment) {
149
+ if (records.length === 0) {
150
+ return inputFailure("No image deployments found in the home store");
151
+ }
152
+ if (records.length > 1) {
153
+ return inputFailure(`status --deployment is required: ${records.map((entry) => entry.record.name).sort().join(", ")}`);
154
+ }
155
+ }
156
+ const targetName = options.deployment ?? records[0].record.name;
157
+ const match = records.find((entry) => entry.record.name === targetName);
158
+ if (!match) {
159
+ return inputFailure(`Unknown deployment "${targetName}". Valid deployments: ${records.map((entry) => entry.record.name).sort().join(", ") || "none"}`);
160
+ }
161
+ let report;
162
+ try {
163
+ const record = await readHomeDeploymentRecord(targetName);
164
+ void record;
165
+ const raw = await readHomeDeploymentReport(targetName);
166
+ let parsed;
167
+ try {
168
+ parsed = JSON.parse(raw);
169
+ }
170
+ catch (error) {
171
+ // A corrupt cached report is bad input, not a runtime failure: exit 2.
172
+ throw new SpawnfileError("validation_error", `Cached report for "${targetName}" is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
173
+ }
174
+ report = parseDistributionReport(parsed);
175
+ }
176
+ catch (error) {
177
+ return { error: error instanceof Error ? error.message : String(error), exitCode: errorExitCode(error) };
178
+ }
179
+ const view = projectImageOrganizationView(report, match.record.source.kind === "image"
180
+ ? match.record.source.ref
181
+ : targetName);
182
+ const reportPath = match.path.replace(/record\.json$/, "spawnfile-report.json");
183
+ const loadedReport = loadedImageCompileReport(report, reportPath);
184
+ const selectedRecords = [match];
185
+ const deploymentInspections = await inspectDeployments(selectedRecords, handlers, options, timeoutMs);
186
+ const recordValues = selectedRecords.map(({ record }) => record);
187
+ const authValues = options.live ? await resolveAuthValues(selectedRecords, handlers) : {};
188
+ const collectRuntimeProbes = handlers.collectRuntimeProbeObservations ?? collectRuntimeProbeObservations;
189
+ const collectMoltnetProbes = handlers.collectMoltnetProbeObservations ?? collectMoltnetProbeObservations;
190
+ const collectDeploymentLogs = handlers.collectDeploymentLogObservations ?? collectDeploymentLogObservations;
191
+ const liveObservations = options.live && !options.recover
192
+ ? [
193
+ ...await collectRuntimeProbes({
194
+ deployments: recordValues,
195
+ inspections: deploymentInspections,
196
+ loadedReport,
197
+ timeoutMs
198
+ }),
199
+ ...await collectMoltnetProbes({
200
+ authValues,
201
+ deployments: recordValues,
202
+ inspections: deploymentInspections,
203
+ loadedReport,
204
+ timeoutMs
205
+ }),
206
+ ...(options.logs
207
+ ? await collectDeploymentLogs({ deployments: recordValues, loadedReport, timeoutMs })
208
+ : []),
209
+ ...(options.pullCheck
210
+ ? await collectRegistryDriftObservations({
211
+ deployments: recordValues,
212
+ dockerCommand: options.dockerCommand,
213
+ timeoutMs
214
+ })
215
+ : [])
216
+ ]
217
+ : [];
218
+ const status = createStaticStatus(view, loadedReport, {
219
+ deployments: createDeploymentSummaries(selectedRecords, deploymentInspections),
220
+ inputPath: view.inputPath,
221
+ live: {
222
+ context: null,
223
+ deploymentName: targetName,
224
+ logs: options.logs ?? false,
225
+ recover: false,
226
+ requested: options.live ?? false
227
+ },
228
+ liveObservations,
229
+ outputDirectory: "",
230
+ selection: null
231
+ });
232
+ return { exitCode: exitCodeForStatus(status), output: renderStatus(status, { mode }), status };
233
+ };
234
+ export const executeStatusCommand = async (inputPath, options, handlers) => {
235
+ const mode = resolveOutputMode(options);
236
+ if (typeof mode !== "string") {
237
+ return mode;
238
+ }
239
+ if (options.logs && !options.live) {
240
+ return inputFailure("status --logs requires --live");
241
+ }
242
+ // A positional image reference without --deployment renders the static
243
+ // interface from the embedded report. Image ref with --deployment, or a bare
244
+ // --deployment on the default path, reads the home store.
245
+ // A clear image reference (tag/digest/registry, or forced with --image)
246
+ // without --deployment renders the static interface; any other positional is
247
+ // treated as a project path (the view builder reports a missing path).
248
+ const commandInput = resolveCommandInput(inputPath, { forceImage: options.image });
249
+ const usedDefaultPath = inputPath === process.cwd();
250
+ if (commandInput.kind === "image" && !options.deployment) {
251
+ return runStaticImageStatus(commandInput.ref, options, mode === "json");
252
+ }
253
+ if (options.deployment && (commandInput.kind === "image" || usedDefaultPath)) {
254
+ const timeoutForHome = resolveTimeoutMs(options);
255
+ if (typeof timeoutForHome !== "number" && timeoutForHome !== undefined) {
256
+ return timeoutForHome;
257
+ }
258
+ return runHomeDeploymentStatus(options, handlers, mode, timeoutForHome);
259
+ }
260
+ if (options.context && !options.recover) {
261
+ return inputFailure("status accepts --context only with --recover");
262
+ }
263
+ if (options.logs && !options.live) {
264
+ return inputFailure("status --logs requires --live");
265
+ }
266
+ if (options.logs && options.recover) {
267
+ return inputFailure("status --logs is not available with --recover");
268
+ }
269
+ if (options.recover && !options.context) {
270
+ return inputFailure("status --recover requires --context");
271
+ }
272
+ const timeoutMs = resolveTimeoutMs(options);
273
+ if (typeof timeoutMs !== "number" && timeoutMs !== undefined) {
274
+ return timeoutMs;
275
+ }
276
+ const selectorInput = resolveSelectorInput(options);
277
+ if (selectorInput && "exitCode" in selectorInput) {
278
+ return selectorInput;
279
+ }
280
+ const outputDirectory = resolveProjectOutputDirectory(inputPath, options.out, DEFAULT_OUTPUT_DIRECTORY);
281
+ const view = await handlers.buildOrganizationView(inputPath);
282
+ const selectorResult = resolveStatusSelector(view, selectorInput);
283
+ if (selectorResult?.kind === "failure") {
284
+ return { error: selectorResult.failure.message, exitCode: 2 };
285
+ }
286
+ const loadedReport = await loadCompileReport(outputDirectory);
287
+ if (loadedReport.kind === "failure") {
288
+ return { error: loadedReport.failure.message, exitCode: 2 };
289
+ }
290
+ let deploymentRecords;
291
+ try {
292
+ deploymentRecords = await listDeploymentRecords(outputDirectory);
293
+ }
294
+ catch (error) {
295
+ return {
296
+ error: error instanceof Error ? error.message : String(error),
297
+ exitCode: 2
298
+ };
299
+ }
300
+ const selectedDeploymentRecords = resolveDeploymentRecords(deploymentRecords, options);
301
+ if ("exitCode" in selectedDeploymentRecords) {
302
+ return selectedDeploymentRecords;
303
+ }
304
+ const deploymentInspections = await inspectDeployments(selectedDeploymentRecords, handlers, options, timeoutMs);
305
+ const selectedDeploymentRecordValues = selectedDeploymentRecords.map(({ record }) => record);
306
+ const authValues = options.live
307
+ ? await resolveAuthValues(selectedDeploymentRecords, handlers)
308
+ : {};
309
+ const collectRuntimeProbes = handlers.collectRuntimeProbeObservations ?? collectRuntimeProbeObservations;
310
+ const collectMoltnetProbes = handlers.collectMoltnetProbeObservations ?? collectMoltnetProbeObservations;
311
+ const collectDeploymentLogs = handlers.collectDeploymentLogObservations ?? collectDeploymentLogObservations;
312
+ const liveObservations = options.live && !options.recover
313
+ ? [
314
+ ...await collectRuntimeProbes({
315
+ deployments: selectedDeploymentRecordValues,
316
+ inspections: deploymentInspections,
317
+ loadedReport,
318
+ timeoutMs
319
+ }),
320
+ ...await collectMoltnetProbes({
321
+ authValues,
322
+ deployments: selectedDeploymentRecordValues,
323
+ inspections: deploymentInspections,
324
+ loadedReport,
325
+ timeoutMs
326
+ }),
327
+ ...(options.logs
328
+ ? await collectDeploymentLogs({
329
+ deployments: selectedDeploymentRecordValues,
330
+ loadedReport,
331
+ timeoutMs
332
+ })
333
+ : [])
334
+ ]
335
+ : [];
336
+ const status = createStaticStatus(view, loadedReport, {
337
+ deployments: createDeploymentSummaries(selectedDeploymentRecords, deploymentInspections),
338
+ inputPath,
339
+ live: {
340
+ context: options.context ?? null,
341
+ deploymentName: options.deployment ?? null,
342
+ logs: options.logs ?? false,
343
+ recover: options.recover ?? false,
344
+ requested: options.live ?? false
345
+ },
346
+ liveObservations,
347
+ outputDirectory,
348
+ selection: selectorResult?.selection ?? null
349
+ });
350
+ return {
351
+ exitCode: exitCodeForStatus(status),
352
+ output: renderStatus(status, { mode }),
353
+ status
354
+ };
355
+ };
356
+ export const registerStatusCommand = (program, handlers, streams, setExitCode) => {
357
+ program
358
+ .command("status")
359
+ .description("Show static Spawnfile organization status")
360
+ .argument("[path]", "Project directory or Spawnfile path", process.cwd())
361
+ .option("--out <dir>", "Compile output directory")
362
+ .option("--json", "Render machine-readable JSON")
363
+ .option("--pretty", "Render human output")
364
+ .option("--quiet", "Render only summary and non-ok observations")
365
+ .option("--live", "Inspect the recorded live deployment")
366
+ .option("--deployment <name>", "Deployment record name")
367
+ .option("--image", "Interpret the argument as an image reference")
368
+ .option("--pull", "Pull the image before inspecting (image references only)")
369
+ .option("--pull-check", "Check the registry for a newer image digest (networked)")
370
+ .option("--docker-command <command>", "Docker command")
371
+ .option("--context <name>", "Docker context for label recovery only")
372
+ .option("--recover", "Recover live status from labels instead of a deployment record")
373
+ .option("--logs", "Include redacted logs when supported")
374
+ .option("--timeout <ms>", "Bound live Docker/runtime checks in milliseconds")
375
+ .option("--watch", "Refresh status every five seconds until interrupted")
376
+ .option("--agent <id>", "Show one agent")
377
+ .option("--team <id>", "Show one team")
378
+ .option("--network <id>", "Show one network")
379
+ .option("--runtime <name>", "Show one runtime")
380
+ .action(async (inputPath, options) => {
381
+ if (options.watch) {
382
+ await executeStatusWatch(inputPath, options, handlers, streams, setExitCode);
383
+ return;
384
+ }
385
+ const result = await executeStatusCommand(inputPath, options, handlers);
386
+ setExitCode(result.exitCode);
387
+ if (result.error) {
388
+ streams.stderr(`error: ${result.error}`);
389
+ }
390
+ if (result.output) {
391
+ emitOutput(streams, result.output);
392
+ }
393
+ });
394
+ };
@@ -22,6 +22,7 @@ export const registerSurfaceCommands = (program, handlers, streams) => {
22
22
  .description("Edit agent surfaces");
23
23
  surfaceCommand
24
24
  .command("add")
25
+ .description("Attach a surface to an agent")
25
26
  .argument("<surface>", "Surface name")
26
27
  .argument("[path]", "Project directory or Spawnfile path", process.cwd())
27
28
  .option("--bot-token-secret <env>", "Override the bot token env var name")
@@ -42,6 +43,7 @@ export const registerSurfaceCommands = (program, handlers, streams) => {
42
43
  });
43
44
  surfaceCommand
44
45
  .command("remove")
46
+ .description("Detach a surface from an agent")
45
47
  .argument("<surface>", "Surface name")
46
48
  .argument("[path]", "Project directory or Spawnfile path", process.cwd())
47
49
  .option("--recursive", "Update the target project and its descendants")
@@ -58,6 +60,7 @@ export const registerSurfaceCommands = (program, handlers, streams) => {
58
60
  });
59
61
  surfaceCommand
60
62
  .command("set-access")
63
+ .description("Set an agent's access mode for a surface")
61
64
  .argument("<surface>", "Surface name")
62
65
  .argument("[path]", "Project directory or Spawnfile path", process.cwd())
63
66
  .requiredOption("--mode <mode>", "Access mode")
@@ -86,6 +89,7 @@ export const registerSurfaceCommands = (program, handlers, streams) => {
86
89
  });
87
90
  surfaceCommand
88
91
  .command("show")
92
+ .description("Show an agent's resolved surfaces")
89
93
  .argument("[path]", "Project directory or Spawnfile path", process.cwd())
90
94
  .option("--recursive", "Show descendant agent surfaces too")
91
95
  .action(async (inputPath, options) => {
@@ -27,8 +27,7 @@ export const resolveAgentSurfaces = (surfaces) => {
27
27
  ? {
28
28
  dms: {
29
29
  enabled: attachment.dms.enabled,
30
- ...(attachment.dms.read ? { read: attachment.dms.read } : {}),
31
- ...(attachment.dms.reply ? { reply: attachment.dms.reply } : {})
30
+ ...(attachment.dms.wake ? { wake: attachment.dms.wake } : {})
32
31
  }
33
32
  }
34
33
  : {}),
@@ -41,8 +40,7 @@ export const resolveAgentSurfaces = (surfaces) => {
41
40
  .map(([roomId, behavior]) => [
42
41
  roomId,
43
42
  {
44
- ...(behavior.read ? { read: behavior.read } : {}),
45
- ...(behavior.reply ? { reply: behavior.reply } : {})
43
+ ...(behavior.wake ? { wake: behavior.wake } : {})
46
44
  }
47
45
  ]))
48
46
  }
@@ -22,9 +22,7 @@ const createTeamOutputDirectory = (baseDirectory, runtimeName, node) => path.joi
22
22
  const compileAgentNode = async (baseDirectory, node) => {
23
23
  const runtime = await assertRuntimeCanCompile(node.runtimeName ?? node.value.runtime.name);
24
24
  const adapter = getRuntimeAdapter(runtime.name);
25
- const diagnostics = [
26
- ...createRuntimeLifecycleDiagnostics(runtime)
27
- ];
25
+ const diagnostics = [...createRuntimeLifecycleDiagnostics(runtime)];
28
26
  for (const diagnostic of adapter.validateRuntimeOptions?.(node.value.runtime.options) ?? []) {
29
27
  diagnostics.push(diagnostic);
30
28
  }
@@ -37,6 +35,7 @@ const compileAgentNode = async (baseDirectory, node) => {
37
35
  await writeEmittedFiles(outputDirectory, result.files);
38
36
  return {
39
37
  emittedFiles: result.files,
38
+ id: node.id,
40
39
  kind: node.kind,
41
40
  report: {
42
41
  capabilities: result.capabilities,
@@ -63,6 +62,7 @@ const compileTeamNode = async (baseDirectory, node) => {
63
62
  if (!runtimeName) {
64
63
  return {
65
64
  emittedFiles: [],
65
+ id: node.id,
66
66
  kind: node.kind,
67
67
  report: {
68
68
  capabilities: createTeamCapabilities("degraded", "Team spans multiple runtimes and cannot lower to one native team artifact in v0.1"),
@@ -88,6 +88,7 @@ const compileTeamNode = async (baseDirectory, node) => {
88
88
  if (!adapter.compileTeam) {
89
89
  return {
90
90
  emittedFiles: [],
91
+ id: node.id,
91
92
  kind: node.kind,
92
93
  report: {
93
94
  capabilities: createTeamCapabilities("degraded", `Runtime ${runtime.name} does not provide native team compilation in v0.1`),
@@ -113,6 +114,7 @@ const compileTeamNode = async (baseDirectory, node) => {
113
114
  await writeEmittedFiles(outputDirectory, result.files);
114
115
  return {
115
116
  emittedFiles: result.files,
117
+ id: node.id,
116
118
  kind: node.kind,
117
119
  report: {
118
120
  capabilities: result.capabilities,
@@ -200,7 +202,11 @@ const augmentNodeReports = (compiledNodes, support) => {
200
202
  };
201
203
  export const compileProject = async (inputPath, options = {}) => {
202
204
  const plan = await buildCompilePlan(inputPath);
203
- const outputDirectory = path.resolve(options.outputDirectory ?? DEFAULT_OUTPUT_DIRECTORY);
205
+ // The default output directory lives under the resolved project root, not the
206
+ // process working directory, so `spawnfile compile ./org` writes ./org/.spawn.
207
+ const outputDirectory = options.outputDirectory
208
+ ? path.resolve(options.outputDirectory)
209
+ : path.join(path.dirname(plan.root), DEFAULT_OUTPUT_DIRECTORY);
204
210
  if (options.clean ?? true) {
205
211
  await removeDirectory(outputDirectory);
206
212
  }
@@ -229,17 +235,20 @@ export const compileProject = async (inputPath, options = {}) => {
229
235
  ? await stageMoltnetBinaries(outputDirectory)
230
236
  : false;
231
237
  await injectMoltnetWorkspaceFiles(outputDirectory, compiledNodes, moltnetArtifacts);
238
+ const generatedAt = new Date().toISOString();
232
239
  const containerArtifacts = await createContainerArtifacts(plan, compiledNodes, {
240
+ generatedAt,
233
241
  hasStagedMoltnetBinaries,
234
242
  moltnet: moltnetArtifacts
235
243
  });
236
244
  await writeEmittedFiles(outputDirectory, containerArtifacts.files);
237
245
  await Promise.all(containerArtifacts.executablePaths.map((filePath) => chmod(path.join(outputDirectory, filePath), 0o755)));
238
- const report = createCompileReport(plan.root, nodeReports, [], containerArtifacts.report);
239
- const reportPath = await writeCompileReport(outputDirectory, report);
240
- return {
246
+ const report = createCompileReport(plan.root, nodeReports, [], containerArtifacts.report, {
247
+ compileFingerprint: containerArtifacts.distribution.fingerprint,
248
+ generatedAt,
241
249
  outputDirectory,
242
- report,
243
- reportPath
244
- };
250
+ projectName: containerArtifacts.distribution.report.organization.project
251
+ });
252
+ const reportPath = await writeCompileReport(outputDirectory, report);
253
+ return { outputDirectory, report, reportPath };
245
254
  };
@@ -3,6 +3,7 @@ import type { CompiledNodeArtifact, GeneratedContainerArtifacts } from "./contai
3
3
  import type { CompilePlan } from "./types.js";
4
4
  export type { CompiledNodeArtifact, GeneratedContainerArtifacts } from "./containerArtifactsTypes.js";
5
5
  export interface ContainerArtifactOptions {
6
+ generatedAt?: string;
6
7
  hasStagedMoltnetBinaries?: boolean;
7
8
  moltnet?: MoltnetArtifacts | null;
8
9
  }