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,119 @@
1
+ import { spawn } from "node:child_process";
2
+ import { dockerContextNameForTarget, dockerHostValueForTarget } from "../deployment/index.js";
3
+ /**
4
+ * Extracts the comparable digest from `docker manifest inspect --verbose` output.
5
+ *
6
+ * A single-arch tag resolves to one manifest object whose `Descriptor.digest`
7
+ * equals the digest RepoDigests records at deploy time. A multi-arch tag resolves
8
+ * to a JSON ARRAY of per-platform manifests; each `Descriptor.digest` is a CHILD
9
+ * digest, never the manifest-list (index) digest that was recorded — comparing one
10
+ * would report drift on every check. We cannot derive the index digest from this
11
+ * command, so multi-arch (and any unparseable output) yields null → "unknown"
12
+ * rather than a false positive.
13
+ */
14
+ export const parseManifestInspectDigest = (output) => {
15
+ try {
16
+ const parsed = JSON.parse(output);
17
+ if (Array.isArray(parsed)) {
18
+ return null;
19
+ }
20
+ const digest = parsed.Descriptor?.digest ??
21
+ parsed.digest;
22
+ return typeof digest === "string" && digest.length > 0 ? digest : null;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ };
28
+ /* v8 ignore start -- the docker spawn boundary is covered by distribution E2E */
29
+ const defaultResolveDigest = async (imageRef, baseArgs) => new Promise((resolve) => {
30
+ const child = spawn("docker", [...baseArgs, "manifest", "inspect", "--verbose", imageRef], {
31
+ stdio: ["ignore", "pipe", "pipe"]
32
+ });
33
+ const stdout = [];
34
+ child.stdout.on("data", (chunk) => stdout.push(String(chunk)));
35
+ child.on("error", () => resolve(null));
36
+ child.on("close", (code) => {
37
+ resolve(code === 0 ? parseManifestInspectDigest(stdout.join("")) : null);
38
+ });
39
+ });
40
+ /* v8 ignore stop */
41
+ const baseArgsForTarget = (record) => {
42
+ const context = dockerContextNameForTarget(record.target);
43
+ if (context) {
44
+ return ["--context", context];
45
+ }
46
+ const host = dockerHostValueForTarget(record.target);
47
+ return host ? ["--host", host] : [];
48
+ };
49
+ /**
50
+ * Compares each image deployment's recorded source.digest against the digest
51
+ * the registry tag currently resolves to. Networked: runs only behind the
52
+ * explicit --pull-check flag. A digest-pinned ref needs no lookup; a null
53
+ * recorded digest renders unknown.
54
+ */
55
+ export const collectRegistryDriftObservations = async (input) => {
56
+ const resolveDigest = input.resolveDigest ?? defaultResolveDigest;
57
+ const observations = [];
58
+ for (const record of input.deployments) {
59
+ if (record.source.kind !== "image") {
60
+ continue;
61
+ }
62
+ const subject = `deployment-unit:${record.name}`;
63
+ const ref = record.source.ref;
64
+ if (ref.includes("@sha256:")) {
65
+ observations.push({
66
+ key: "registry.drift",
67
+ label: "registry",
68
+ message: `${ref} is digest-pinned; registry drift does not apply`,
69
+ severity: "ok",
70
+ source: "deployment",
71
+ subject
72
+ });
73
+ continue;
74
+ }
75
+ if (!record.source.digest) {
76
+ observations.push({
77
+ key: "registry.drift",
78
+ label: "registry",
79
+ message: `${ref} has no recorded registry digest; drift is unknown`,
80
+ severity: "unknown",
81
+ source: "deployment",
82
+ subject
83
+ });
84
+ continue;
85
+ }
86
+ const current = await resolveDigest(ref, baseArgsForTarget(record));
87
+ if (!current) {
88
+ observations.push({
89
+ key: "registry.drift",
90
+ label: "registry",
91
+ message: `Unable to resolve the current registry digest for ${ref}`,
92
+ severity: "unknown",
93
+ source: "deployment",
94
+ subject
95
+ });
96
+ continue;
97
+ }
98
+ if (current === record.source.digest) {
99
+ observations.push({
100
+ key: "registry.drift",
101
+ label: "registry",
102
+ message: `${ref} matches the recorded registry digest`,
103
+ severity: "ok",
104
+ source: "deployment",
105
+ subject
106
+ });
107
+ continue;
108
+ }
109
+ observations.push({
110
+ key: "registry.drift",
111
+ label: "registry",
112
+ message: `A newer build of ${ref} has been published since this deployment`,
113
+ severity: "warn",
114
+ source: "deployment",
115
+ subject
116
+ });
117
+ }
118
+ return observations;
119
+ };
@@ -0,0 +1,2 @@
1
+ import type { StaticStatus, StatusRenderOptions } from "./types.js";
2
+ export declare const renderStatus: (status: StaticStatus, options: StatusRenderOptions) => string;
@@ -0,0 +1,230 @@
1
+ import path from "node:path";
2
+ import { renderOrganizationTree } from "../compiler/index.js";
3
+ import { countObservations, getVisibleObservations } from "./buildStatus.js";
4
+ import { flattenOrganizationNodes } from "./traversal.js";
5
+ const SEVERITY_ORDER = ["error", "warn", "unknown", "ok"];
6
+ const relativePath = (value, root) => {
7
+ if (!root) {
8
+ return value;
9
+ }
10
+ const relative = path.relative(root, value) || ".";
11
+ // A relative path that escapes the project root (e.g. an --out under /tmp) is
12
+ // never more readable than the absolute path — show the absolute one instead.
13
+ return relative.startsWith("..") ? value : relative;
14
+ };
15
+ const formatCounts = (counts) => `${counts.ok} ok, ${counts.warn} warn, ${counts.error} error, ${counts.unknown} unknown`;
16
+ const severityForSubject = (observations, subject, source) => {
17
+ const subjectObservations = observations.filter((observation) => observation.subject === subject && (!source || observation.source === source));
18
+ for (const severity of SEVERITY_ORDER) {
19
+ if (subjectObservations.some((observation) => observation.severity === severity)) {
20
+ return severity;
21
+ }
22
+ }
23
+ return "unknown";
24
+ };
25
+ const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26
+ const humanizeMessagePaths = (message, projectRoot) => {
27
+ if (!projectRoot) {
28
+ return message;
29
+ }
30
+ return message.replace(new RegExp(escapeRegExp(projectRoot), "g"), ".");
31
+ };
32
+ const formatObservation = (observation, projectRoot) => `[${observation.severity}] ${observation.subject} ${observation.key}: ${humanizeMessagePaths(observation.message, projectRoot)}`;
33
+ const nonOkObservations = (observations) => observations.filter((observation) => observation.severity !== "ok");
34
+ const observationsWithDetails = (observations) => observations.filter((observation) => observation.details && Object.keys(observation.details).length > 0);
35
+ const formatDetailValue = (value) => typeof value === "string" ? value : JSON.stringify(value);
36
+ const formatObservationDetails = (observation, projectRoot) => {
37
+ const details = observation.details ?? {};
38
+ const lines = [` ${formatObservation(observation, projectRoot)}`];
39
+ const logTail = details.log_tail;
40
+ if (typeof logTail === "string") {
41
+ lines.push(" log_tail:");
42
+ const text = logTail.replace(/\n$/u, "");
43
+ if (text.length === 0) {
44
+ lines.push(" (empty)");
45
+ }
46
+ else {
47
+ lines.push(...text.split("\n").map((line) => ` ${humanizeMessagePaths(line, projectRoot)}`));
48
+ }
49
+ }
50
+ for (const [key, value] of Object.entries(details)) {
51
+ if (key === "log_tail") {
52
+ continue;
53
+ }
54
+ lines.push(` ${key}: ${humanizeMessagePaths(formatDetailValue(value), projectRoot)}`);
55
+ }
56
+ return lines;
57
+ };
58
+ const formatDocRoles = (node) => node.declared?.docs.map((doc) => doc.role).sort().join(", ") || "none";
59
+ const formatNodeDetails = (node, observations, projectRoot) => {
60
+ const declared = node.declared;
61
+ const details = [
62
+ ` ${node.name} (${node.id})${node.runtimeName ? ` runtime=${node.runtimeName}` : ""}`,
63
+ ` source: ${relativePath(node.source, projectRoot)}`,
64
+ ` declared: ${severityForSubject(observations, node.id, "declared")}`,
65
+ ` compiled: ${severityForSubject(observations, node.id, "compile_report")}`,
66
+ ` docs: ${formatDocRoles(node)}`
67
+ ];
68
+ if (!declared) {
69
+ return details;
70
+ }
71
+ details.push(` skills: ${declared.skills.map((skill) => skill.name).join(", ") || "none"}`);
72
+ details.push(` resources: ${declared.resources.map((resource) => `${resource.id}@${resource.mount}(${resource.sharing})`).join(", ") || "none"}`);
73
+ details.push(` packages: ${declared.packages.map((pkg) => pkg.id).join(", ") || "none"}`);
74
+ details.push(` mcp: ${declared.mcpServers.map((server) => `${server.name}:${server.transport}`).join(", ") || "none"}`);
75
+ details.push(` model: ${declared.model
76
+ ? `${declared.model.provider}/${declared.model.name} auth=${declared.model.authMethod}`
77
+ : "none"}`);
78
+ details.push(` schedule: ${declared.schedule
79
+ ? `${declared.schedule.kind}${declared.schedule.expression ? ` ${declared.schedule.expression}` : ""}`
80
+ : "none"}`);
81
+ details.push(` surfaces: ${declared.surfaces.map((surface) => `${surface.name}[${surface.scopes.join(",")}]`).join(", ") || "none"}`);
82
+ details.push(` policy: mode=${declared.policy.mode ?? "default"} on_degrade=${declared.policy.onDegrade ?? "default"}`);
83
+ return details;
84
+ };
85
+ const formatNetwork = (network, observations) => [
86
+ ` ${network.id} "${network.name}"`,
87
+ ` declared: ${severityForSubject(observations, `network:${network.id}`, "declared")}`,
88
+ ` provider: ${network.provider}`,
89
+ ` server: ${network.serverMode ?? "unspecified"}`,
90
+ ` rooms: ${network.rooms.map((room) => `${room.id}[${room.declaredMembers.join(",")}]`).join(", ") || "none"}`
91
+ ];
92
+ const formatRuntime = (runtime, observations) => [
93
+ ` ${runtime.name}`,
94
+ ` declared: ${severityForSubject(observations, `runtime:${runtime.name}`, "declared")}`,
95
+ ` nodes: ${runtime.nodeIds.join(", ") || "none"}`
96
+ ];
97
+ const formatDeployment = (deployment, projectRoot) => [
98
+ ` ${deployment.name} ${deployment.manager} ${deployment.target}`,
99
+ ` record: ${relativePath(deployment.recordPath, projectRoot)}`,
100
+ ` compile: ${deployment.compileFingerprint}`,
101
+ ` created: ${deployment.createdAt}`,
102
+ ` auth-profile: ${deployment.authProfile ?? "none"}`,
103
+ ...deployment.units.flatMap((unit) => [
104
+ ` unit ${unit.id}`,
105
+ ` image: ${unit.imageTag}${unit.imageId ? ` (${unit.imageId})` : ""}`,
106
+ ` container: ${unit.containerName ?? "none"}${unit.containerId ? ` (${unit.containerId})` : ""}`,
107
+ ` contains: ${unit.contains.map((entry) => entry.id).join(", ") || "none"}`,
108
+ ` runtimes: ${unit.runtimeInstances.join(", ") || "none"}`,
109
+ ` live: ${unit.live ? unit.live.message : "not checked"}`
110
+ ])
111
+ ];
112
+ const renderHeader = (status, observations) => {
113
+ const counts = countObservations(observations);
114
+ const count = (value, noun) => `${value} ${noun}${value === 1 ? "" : "s"}`;
115
+ return [
116
+ `Spawnfile status: ${relativePath(status.inputPath, status.projectRoot)}`,
117
+ `Output: ${relativePath(status.outputDirectory, status.projectRoot)}`,
118
+ `Summary: ${count(status.summary.agents, "agent")}, ${count(status.summary.teams, "team")}, ${count(status.summary.networks, "network")}, ${count(status.summary.runtimes, "runtime")}, ${count(status.summary.deployments, "deployment")}`,
119
+ `Status: ${formatCounts(counts)}`,
120
+ status.live.requested ? `Live: requested${status.live.deploymentName ? ` deployment=${status.live.deploymentName}` : ""}` : "",
121
+ status.selection ? `Selection: ${status.selection.kind} ${status.selection.label}` : ""
122
+ ].filter(Boolean);
123
+ };
124
+ const selectedNodes = (status) => {
125
+ const nodes = flattenOrganizationNodes(status.view);
126
+ if (!status.selection) {
127
+ return nodes;
128
+ }
129
+ if (status.selection.kind === "agent" || status.selection.kind === "team") {
130
+ const subjects = new Set(status.selection.subjectKeys);
131
+ return nodes.filter((node) => subjects.has(node.id));
132
+ }
133
+ if (status.selection.kind === "runtime") {
134
+ const subjects = new Set(status.selection.subjectKeys);
135
+ return nodes.filter((node) => subjects.has(node.id));
136
+ }
137
+ return [];
138
+ };
139
+ const selectedNetworks = (status) => {
140
+ if (status.selection?.kind !== "network") {
141
+ return status.selection ? [] : status.view.networks;
142
+ }
143
+ const subjects = new Set(status.selection.subjectKeys);
144
+ return status.view.networks.filter((network) => subjects.has(`network:${network.id}`));
145
+ };
146
+ const selectedRuntimes = (status) => {
147
+ if (status.selection?.kind !== "runtime") {
148
+ return status.selection ? [] : status.view.runtimes;
149
+ }
150
+ return status.view.runtimes.filter((runtime) => runtime.name === status.selection?.value);
151
+ };
152
+ const selectedDeployments = (status) => {
153
+ if (!status.selection) {
154
+ return status.deployments;
155
+ }
156
+ if (status.selection.kind === "network") {
157
+ return [];
158
+ }
159
+ const subjects = new Set(status.selection.subjectKeys);
160
+ return status.deployments.filter((deployment) => deployment.units.some((unit) => unit.contains.some((entry) => subjects.has(entry.id))
161
+ || unit.runtimeInstances.some((runtimeInstance) => subjects.has(`runtime-instance:${runtimeInstance}`))));
162
+ };
163
+ const renderPrettyStatus = (status) => {
164
+ const observations = getVisibleObservations(status);
165
+ const annotations = (subject) => {
166
+ const severity = severityForSubject(observations, subject);
167
+ return [`status=${severity}`];
168
+ };
169
+ const lines = [
170
+ ...renderHeader(status, observations),
171
+ "",
172
+ `Compile: ${status.compile.present ? "present" : "missing"} ${relativePath(status.compile.path, status.projectRoot)}`,
173
+ ...(status.compile.present ? [] : [" run 'spawnfile compile' to populate compiled status"])
174
+ ];
175
+ if (!status.selection) {
176
+ lines.push("", "Organization", renderOrganizationTree(status.view, { annotationFor: annotations }));
177
+ }
178
+ const deployments = selectedDeployments(status);
179
+ if (deployments.length > 0) {
180
+ lines.push("", "Deployments", ...deployments.flatMap((deployment) => formatDeployment(deployment, status.projectRoot)));
181
+ }
182
+ const nodes = selectedNodes(status);
183
+ if (nodes.length > 0) {
184
+ lines.push("", "Nodes", ...nodes.flatMap((node) => formatNodeDetails(node, observations, status.projectRoot)));
185
+ }
186
+ const networks = selectedNetworks(status);
187
+ if (networks.length > 0) {
188
+ lines.push("", "Networks", ...networks.flatMap((network) => formatNetwork(network, observations)));
189
+ }
190
+ const runtimes = selectedRuntimes(status);
191
+ if (runtimes.length > 0) {
192
+ lines.push("", "Runtimes", ...runtimes.flatMap((runtime) => formatRuntime(runtime, observations)));
193
+ }
194
+ const notable = nonOkObservations(observations);
195
+ if (notable.length > 0) {
196
+ lines.push("", "Observations", ...notable.map((observation) => ` ${formatObservation(observation, status.projectRoot)}`));
197
+ }
198
+ const details = observationsWithDetails(observations);
199
+ if (details.length > 0) {
200
+ lines.push("", "Details", ...details.flatMap((observation) => formatObservationDetails(observation, status.projectRoot)));
201
+ }
202
+ return lines.join("\n");
203
+ };
204
+ const renderQuietStatus = (status) => {
205
+ const observations = getVisibleObservations(status);
206
+ const notable = nonOkObservations(observations);
207
+ return [
208
+ ...renderHeader(status, observations),
209
+ ...notable.map((observation) => formatObservation(observation, status.projectRoot))
210
+ ].join("\n");
211
+ };
212
+ const renderJsonStatus = (status) => JSON.stringify({
213
+ compile: status.compile,
214
+ deployments: status.deployments,
215
+ input_path: status.inputPath,
216
+ live: status.live,
217
+ observations: getVisibleObservations(status),
218
+ output_directory: status.outputDirectory,
219
+ project_root: status.projectRoot,
220
+ selection: status.selection,
221
+ summary: status.summary,
222
+ version: status.version,
223
+ view: status.view
224
+ }, null, 2);
225
+ export const renderStatus = (status, options) => {
226
+ if (options.mode === "json") {
227
+ return renderJsonStatus(status);
228
+ }
229
+ return options.mode === "quiet" ? renderQuietStatus(status) : renderPrettyStatus(status);
230
+ };
@@ -0,0 +1,13 @@
1
+ import type { DeploymentRecord, DockerInspectionResult, DockerProbeExecFile } from "../deployment/index.js";
2
+ import type { LoadedCompileReport } from "./compileReport.js";
3
+ import type { StatusObservation } from "./types.js";
4
+ export type StatusRuntimeInspections = Map<string, DockerInspectionResult>;
5
+ export interface CollectRuntimeProbeOptions {
6
+ deployments: DeploymentRecord[];
7
+ dockerCommand?: string;
8
+ execFile?: DockerProbeExecFile;
9
+ inspections: StatusRuntimeInspections;
10
+ loadedReport: LoadedCompileReport;
11
+ timeoutMs?: number;
12
+ }
13
+ export declare const collectRuntimeProbeObservations: (options: CollectRuntimeProbeOptions) => Promise<StatusObservation[]>;
@@ -0,0 +1,111 @@
1
+ import { createDockerProbeGateway } from "../deployment/index.js";
2
+ import { getRuntimeAdapter } from "../runtime/index.js";
3
+ const createObservation = (input) => ({
4
+ ...input,
5
+ label: `${input.severity.toUpperCase()} ${input.key}`,
6
+ source: "runtime"
7
+ });
8
+ const instanceSubject = (instanceId) => `runtime-instance:${instanceId}`;
9
+ const fromProbeObservation = (instance, probe) => createObservation({
10
+ ...(probe.details ? { details: probe.details } : {}),
11
+ key: probe.key,
12
+ message: probe.message,
13
+ severity: probe.severity,
14
+ subject: instanceSubject(instance.id)
15
+ });
16
+ const toRuntimeInstanceReport = (instance) => ({
17
+ config_path: instance.configPath ?? "",
18
+ home_path: instance.homePath,
19
+ id: instance.id,
20
+ internal_port: instance.internalPort,
21
+ model_auth_methods: {},
22
+ model_secrets_required: [],
23
+ node_ids: instance.nodeIds,
24
+ published_port: instance.publishedPort,
25
+ runtime: instance.runtime,
26
+ workspace_path: instance.workspacePath ?? undefined
27
+ });
28
+ const skippedSeverity = (running) => running === false ? "error" : "unknown";
29
+ export const collectRuntimeProbeObservations = async (options) => {
30
+ if (options.loadedReport.kind !== "loaded") {
31
+ return [];
32
+ }
33
+ const observations = [];
34
+ const instances = new Map(options.loadedReport.report.runtimeInstances.map((instance) => [instance.id, instance]));
35
+ for (const deployment of options.deployments) {
36
+ const deploymentInspections = options.inspections.get(deployment.name);
37
+ for (const unit of deployment.units) {
38
+ const inspection = deploymentInspections?.get(unit.id);
39
+ for (const instanceId of unit.runtime_instances) {
40
+ const instance = instances.get(instanceId);
41
+ if (!instance) {
42
+ observations.push(createObservation({
43
+ key: "runtime.instance",
44
+ message: `${instanceId} is referenced by deployment ${deployment.name}/${unit.id} but missing from the compile report`,
45
+ severity: "warn",
46
+ subject: instanceSubject(instanceId)
47
+ }));
48
+ continue;
49
+ }
50
+ if (inspection?.running !== true) {
51
+ observations.push(createObservation({
52
+ key: "runtime.probe",
53
+ message: `${instance.id} runtime probe skipped because deployment unit ${unit.id} is not running`,
54
+ severity: skippedSeverity(inspection?.running),
55
+ subject: instanceSubject(instance.id)
56
+ }));
57
+ continue;
58
+ }
59
+ let adapter;
60
+ try {
61
+ adapter = getRuntimeAdapter(instance.runtime);
62
+ }
63
+ catch (error) {
64
+ observations.push(createObservation({
65
+ key: "runtime.adapter",
66
+ message: error instanceof Error ? error.message : String(error),
67
+ severity: "unknown",
68
+ subject: instanceSubject(instance.id)
69
+ }));
70
+ continue;
71
+ }
72
+ if (!adapter.statusProbes || adapter.statusProbes.length === 0) {
73
+ observations.push(createObservation({
74
+ key: "runtime.probe",
75
+ message: `${instance.runtime} has no status probes`,
76
+ severity: "unknown",
77
+ subject: instanceSubject(instance.id)
78
+ }));
79
+ continue;
80
+ }
81
+ const manager = createDockerProbeGateway(deployment, unit, {
82
+ dockerCommand: options.dockerCommand,
83
+ execFile: options.execFile,
84
+ inspection,
85
+ timeoutMs: options.timeoutMs
86
+ });
87
+ for (const probe of adapter.statusProbes) {
88
+ try {
89
+ const probeObservations = await probe.run({
90
+ deployment,
91
+ instance: toRuntimeInstanceReport(instance),
92
+ manager,
93
+ timeoutMs: options.timeoutMs ?? 10_000,
94
+ unit
95
+ });
96
+ observations.push(...probeObservations.map((entry) => fromProbeObservation(instance, entry)));
97
+ }
98
+ catch (error) {
99
+ observations.push(createObservation({
100
+ key: `runtime.${probe.id}`,
101
+ message: `${probe.label} probe failed: ${error instanceof Error ? error.message : String(error)}`,
102
+ severity: "unknown",
103
+ subject: instanceSubject(instance.id)
104
+ }));
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
110
+ return observations;
111
+ };
@@ -0,0 +1,8 @@
1
+ import type { OrganizationView } from "../compiler/index.js";
2
+ import type { LoadedCompileReport } from "./compileReport.js";
3
+ import type { StatusDeploymentSummary, StatusSelection } from "./types.js";
4
+ export declare const expandStatusSelectionSubjects: (selection: StatusSelection | null, input: {
5
+ deployments: StatusDeploymentSummary[];
6
+ loadedReport: LoadedCompileReport;
7
+ view: OrganizationView;
8
+ }) => StatusSelection | null;
@@ -0,0 +1,77 @@
1
+ const addRuntimeInstancesForNodes = (subjects, loadedReport, nodeIds) => {
2
+ if (loadedReport.kind !== "loaded") {
3
+ return;
4
+ }
5
+ for (const instance of loadedReport.report.runtimeInstances) {
6
+ if (instance.nodeIds.some((nodeId) => nodeIds.has(nodeId))) {
7
+ subjects.add(`runtime-instance:${instance.id}`);
8
+ }
9
+ }
10
+ };
11
+ const addRuntimeInstancesForRuntime = (subjects, loadedReport, runtimeName) => {
12
+ if (loadedReport.kind !== "loaded") {
13
+ return;
14
+ }
15
+ for (const instance of loadedReport.report.runtimeInstances) {
16
+ if (instance.runtime === runtimeName) {
17
+ subjects.add(`runtime-instance:${instance.id}`);
18
+ instance.nodeIds.forEach((nodeId) => subjects.add(nodeId));
19
+ }
20
+ }
21
+ };
22
+ const addDeploymentSubjects = (subjects, deployments) => {
23
+ for (const deployment of deployments) {
24
+ let deploymentMatches = false;
25
+ for (const unit of deployment.units) {
26
+ const unitMatches = unit.contains.some((entry) => subjects.has(entry.id))
27
+ || unit.runtimeInstances.some((instanceId) => subjects.has(`runtime-instance:${instanceId}`));
28
+ if (!unitMatches) {
29
+ continue;
30
+ }
31
+ deploymentMatches = true;
32
+ subjects.add(`deployment-unit:${deployment.name}:${unit.id}`);
33
+ unit.runtimeInstances.forEach((instanceId) => subjects.add(`runtime-instance:${instanceId}`));
34
+ unit.contains.forEach((entry) => subjects.add(entry.id));
35
+ }
36
+ if (deploymentMatches) {
37
+ subjects.add(`deployment:${deployment.name}`);
38
+ }
39
+ }
40
+ };
41
+ const addNetworkSubjects = (subjects, view, loadedReport, networkId) => {
42
+ const addRoom = (roomId, members) => {
43
+ subjects.add(`room:${networkId}:${roomId}`);
44
+ members.forEach((member) => subjects.add(`agent:${member}`));
45
+ };
46
+ const declaredNetwork = view.networks.find((network) => network.id === networkId);
47
+ declaredNetwork?.rooms.forEach((room) => addRoom(room.id, room.declaredMembers));
48
+ if (loadedReport.kind !== "loaded") {
49
+ return;
50
+ }
51
+ for (const server of loadedReport.report.moltnetServers ?? []) {
52
+ if (server.networkId === networkId) {
53
+ server.rooms.forEach((room) => addRoom(room.id, room.members));
54
+ }
55
+ }
56
+ };
57
+ export const expandStatusSelectionSubjects = (selection, input) => {
58
+ if (!selection) {
59
+ return null;
60
+ }
61
+ const subjects = new Set(selection.subjectKeys);
62
+ const nodeIds = new Set(selection.subjectKeys.filter((subject) => subject.startsWith("agent:") || subject.startsWith("team:")));
63
+ if (nodeIds.size > 0) {
64
+ addRuntimeInstancesForNodes(subjects, input.loadedReport, nodeIds);
65
+ }
66
+ if (selection.kind === "runtime") {
67
+ addRuntimeInstancesForRuntime(subjects, input.loadedReport, selection.value);
68
+ }
69
+ if (selection.kind === "network") {
70
+ addNetworkSubjects(subjects, input.view, input.loadedReport, selection.value);
71
+ }
72
+ addDeploymentSubjects(subjects, input.deployments);
73
+ return {
74
+ ...selection,
75
+ subjectKeys: [...subjects]
76
+ };
77
+ };
@@ -0,0 +1,14 @@
1
+ import type { OrganizationView } from "../compiler/index.js";
2
+ import type { StatusInputFailure, StatusSelection, StatusSelectorKind } from "./types.js";
3
+ export interface StatusSelectorInput {
4
+ kind: StatusSelectorKind;
5
+ value: string;
6
+ }
7
+ export type StatusSelectorResult = {
8
+ kind: "failure";
9
+ failure: StatusInputFailure;
10
+ } | {
11
+ kind: "selected";
12
+ selection: StatusSelection;
13
+ };
14
+ export declare const resolveStatusSelector: (view: OrganizationView, selector: StatusSelectorInput | null) => StatusSelectorResult | null;
@@ -0,0 +1,90 @@
1
+ import { flattenOrganizationNodes } from "./traversal.js";
2
+ const failure = (message) => ({
3
+ failure: { exitCode: 2, message },
4
+ kind: "failure"
5
+ });
6
+ const validValuesMessage = (label, values) => values.length > 0 ? `${label}: ${values.join(", ")}` : `${label}: none`;
7
+ const selectNode = (nodes, kind, value) => {
8
+ const candidates = nodes.filter((node) => node.kind === kind);
9
+ const exactId = candidates.filter((node) => node.id === value);
10
+ if (exactId.length === 1) {
11
+ const [node] = exactId;
12
+ return {
13
+ kind: "selected",
14
+ selection: { kind, label: node.displayName, subjectKeys: [node.id], value }
15
+ };
16
+ }
17
+ const named = candidates.filter((node) => node.name === value || node.slug === value);
18
+ if (named.length === 1) {
19
+ const [node] = named;
20
+ return {
21
+ kind: "selected",
22
+ selection: { kind, label: node.displayName, subjectKeys: [node.id], value }
23
+ };
24
+ }
25
+ if (named.length > 1 || exactId.length > 1) {
26
+ const matches = [...exactId, ...named].map((node) => node.id).sort();
27
+ return failure(`Ambiguous ${kind} selector "${value}". Candidates: ${matches.join(", ")}`);
28
+ }
29
+ return failure(`Unknown ${kind} selector "${value}". ${validValuesMessage(`Valid ${kind} ids`, candidates.map((node) => node.id).sort())}`);
30
+ };
31
+ const selectNetwork = (view, value) => {
32
+ const exactId = view.networks.filter((network) => network.id === value);
33
+ if (exactId.length === 1) {
34
+ const [network] = exactId;
35
+ return {
36
+ kind: "selected",
37
+ selection: {
38
+ kind: "network",
39
+ label: network.name,
40
+ subjectKeys: [`network:${network.id}`],
41
+ value
42
+ }
43
+ };
44
+ }
45
+ const named = view.networks.filter((network) => network.name === value);
46
+ if (named.length === 1) {
47
+ const [network] = named;
48
+ return {
49
+ kind: "selected",
50
+ selection: {
51
+ kind: "network",
52
+ label: network.name,
53
+ subjectKeys: [`network:${network.id}`],
54
+ value
55
+ }
56
+ };
57
+ }
58
+ if (named.length > 1 || exactId.length > 1) {
59
+ const matches = [...exactId, ...named].map((network) => network.id).sort();
60
+ return failure(`Ambiguous network selector "${value}". Candidates: ${matches.join(", ")}`);
61
+ }
62
+ return failure(`Unknown network selector "${value}". ${validValuesMessage("Valid network ids", view.networks.map((network) => network.id).sort())}`);
63
+ };
64
+ const selectRuntime = (view, value) => {
65
+ const runtime = view.runtimes.find((entry) => entry.name === value);
66
+ if (!runtime) {
67
+ return failure(`Unknown runtime selector "${value}". ${validValuesMessage("Valid runtime names", view.runtimes.map((entry) => entry.name).sort())}`);
68
+ }
69
+ return {
70
+ kind: "selected",
71
+ selection: {
72
+ kind: "runtime",
73
+ label: runtime.name,
74
+ subjectKeys: [`runtime:${runtime.name}`, ...runtime.nodeIds],
75
+ value
76
+ }
77
+ };
78
+ };
79
+ export const resolveStatusSelector = (view, selector) => {
80
+ if (!selector) {
81
+ return null;
82
+ }
83
+ if (selector.kind === "network") {
84
+ return selectNetwork(view, selector.value);
85
+ }
86
+ if (selector.kind === "runtime") {
87
+ return selectRuntime(view, selector.value);
88
+ }
89
+ return selectNode(flattenOrganizationNodes(view), selector.kind, selector.value);
90
+ };