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,19 @@
1
+ export interface DockerRunInvocation {
2
+ args: string[];
3
+ command: string;
4
+ containerName: string | null;
5
+ cwd: string;
6
+ detach: boolean;
7
+ deploymentName?: string | null;
8
+ dockerContext?: string | null;
9
+ dockerHost?: string | null;
10
+ envFilePath: string;
11
+ imageTag: string;
12
+ supportDirectory: string;
13
+ }
14
+ export interface DockerRunResult {
15
+ containerId?: string;
16
+ imageId?: string;
17
+ }
18
+ export type DockerRunRunner = (invocation: DockerRunInvocation) => Promise<DockerRunResult | void>;
19
+ export declare const runDockerContainer: DockerRunRunner;
@@ -0,0 +1,57 @@
1
+ import { execFile as execFileCallback, spawn } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { SpawnfileError } from "../shared/index.js";
4
+ const execFile = promisify(execFileCallback);
5
+ const inspectImageArgs = (invocation, containerId) => {
6
+ const base = invocation.dockerContext
7
+ ? ["--context", invocation.dockerContext, "inspect"]
8
+ : invocation.dockerHost
9
+ ? ["--host", invocation.dockerHost, "inspect"]
10
+ : ["inspect"];
11
+ return [...base, "--format", "{{.Image}}", containerId];
12
+ };
13
+ const inspectDetachedImageId = async (invocation, containerId) => {
14
+ try {
15
+ const { stdout } = await execFile(invocation.command, inspectImageArgs(invocation, containerId), {
16
+ cwd: invocation.cwd,
17
+ timeout: 10_000
18
+ });
19
+ return stdout.trim() || undefined;
20
+ }
21
+ catch (error) {
22
+ const message = error instanceof Error ? error.message : String(error);
23
+ throw new SpawnfileError("runtime_error", `Unable to inspect detached container ${containerId} image id: ${message}`);
24
+ }
25
+ };
26
+ export const runDockerContainer = async (invocation) => new Promise((resolve, reject) => {
27
+ let stdout = "";
28
+ const child = spawn(invocation.command, invocation.args, {
29
+ cwd: invocation.cwd,
30
+ stdio: invocation.detach ? ["ignore", "pipe", "inherit"] : "inherit"
31
+ });
32
+ if (invocation.detach && child.stdout) {
33
+ child.stdout.setEncoding("utf8");
34
+ child.stdout.on("data", (chunk) => {
35
+ stdout += chunk;
36
+ });
37
+ }
38
+ child.once("error", (error) => {
39
+ reject(new SpawnfileError("runtime_error", `Unable to start docker run for ${invocation.imageTag}: ${error.message}`));
40
+ });
41
+ child.once("exit", (code, signal) => {
42
+ if (code === 0) {
43
+ const containerId = stdout.trim().split(/\s+/)[0];
44
+ if (!invocation.detach || !containerId) {
45
+ resolve(undefined);
46
+ return;
47
+ }
48
+ inspectDetachedImageId(invocation, containerId)
49
+ .then((imageId) => resolve({ containerId, ...(imageId ? { imageId } : {}) }))
50
+ .catch(reject);
51
+ return;
52
+ }
53
+ reject(new SpawnfileError("runtime_error", signal
54
+ ? `Docker run for ${invocation.imageTag} exited from signal ${signal}`
55
+ : `Docker run for ${invocation.imageTag} failed with exit code ${code ?? "unknown"}`));
56
+ });
57
+ });
@@ -82,8 +82,7 @@ export interface ResolvedHttpSurface {
82
82
  port?: number;
83
83
  }
84
84
  export interface ResolvedMoltnetRoomPolicy {
85
- read?: "all" | "mentions" | "thread_only";
86
- reply?: "auto" | "never";
85
+ wake?: "all" | "mentions" | "thread_only" | "never";
87
86
  }
88
87
  export interface ResolvedMoltnetDMConfig extends ResolvedMoltnetRoomPolicy {
89
88
  enabled: boolean;
@@ -1,3 +1,4 @@
1
+ import { type DockerTargetExecFile } from "../deployment/index.js";
1
2
  import { type BuildProjectResult, type DockerBuildRunner } from "./buildProject.js";
2
3
  import { type DockerRunRunner } from "./runProject.js";
3
4
  import { CompileProjectOptions } from "./compileProject.js";
@@ -6,14 +7,19 @@ export interface UpProjectOptions extends CompileProjectOptions {
6
7
  buildRunner?: DockerBuildRunner;
7
8
  containerName?: string;
8
9
  detach?: boolean;
10
+ deploymentName?: string;
9
11
  dockerCommand?: string;
12
+ dockerContext?: string;
13
+ dockerHost?: string;
10
14
  envFilePath?: string;
11
15
  imageTag?: string;
12
16
  runRunner?: DockerRunRunner;
17
+ targetExecFile?: DockerTargetExecFile;
13
18
  }
14
19
  export interface UpProjectResult extends BuildProjectResult {
15
20
  authProfileName: string | null;
16
21
  containerName: string | null;
22
+ deploymentRecordPath?: string | null;
17
23
  supportDirectory: string | null;
18
24
  }
19
25
  export declare const upProject: (inputPath: string, options?: UpProjectOptions) => Promise<UpProjectResult>;
@@ -1,37 +1,71 @@
1
+ import path from "node:path";
1
2
  import { removeDirectory } from "../filesystem/index.js";
3
+ import { writeDockerDeploymentRecordForRun } from "../deployment/index.js";
2
4
  import { buildProject } from "./buildProject.js";
3
- import { createDockerRunInvocation, runDockerContainer } from "./runProject.js";
5
+ import { createDockerRunInvocation, resolveDetachedDeploymentOptions, runDockerContainer } from "./runProject.js";
4
6
  import { requireAuthProfile } from "../auth/index.js";
7
+ import { DEFAULT_OUTPUT_DIRECTORY } from "../shared/index.js";
5
8
  export const upProject = async (inputPath, options = {}) => {
9
+ const resolvedOptions = await resolveDetachedDeploymentOptions(path.resolve(options.outputDirectory ?? DEFAULT_OUTPUT_DIRECTORY), {
10
+ authProfile: options.authProfile,
11
+ containerName: options.containerName,
12
+ deploymentName: options.deploymentName,
13
+ detach: options.detach,
14
+ dockerCommand: options.dockerCommand,
15
+ dockerContext: options.dockerContext,
16
+ dockerHost: options.dockerHost,
17
+ envFilePath: options.envFilePath,
18
+ imageTag: options.imageTag,
19
+ targetExecFile: options.targetExecFile
20
+ });
6
21
  const buildResult = await buildProject(inputPath, {
7
22
  buildRunner: options.buildRunner,
8
23
  clean: options.clean,
9
24
  dockerCommand: options.dockerCommand,
10
- imageTag: options.imageTag,
25
+ imageTag: resolvedOptions.imageTag,
11
26
  outputDirectory: options.outputDirectory
12
27
  });
13
- const authProfile = options.authProfile
14
- ? await requireAuthProfile(options.authProfile)
28
+ const authProfile = resolvedOptions.authProfile
29
+ ? await requireAuthProfile(resolvedOptions.authProfile)
15
30
  : null;
16
- const invocation = await createDockerRunInvocation(buildResult, buildResult.imageTag, {
31
+ const imageTag = resolvedOptions.imageTag ?? buildResult.imageTag;
32
+ const invocation = await createDockerRunInvocation(buildResult, imageTag, {
17
33
  authProfile,
18
- containerName: options.containerName,
34
+ containerName: resolvedOptions.containerName,
19
35
  detach: options.detach,
36
+ deploymentName: resolvedOptions.deploymentName,
20
37
  dockerCommand: options.dockerCommand,
21
- envFilePath: options.envFilePath
38
+ dockerContext: resolvedOptions.dockerContext,
39
+ dockerHost: resolvedOptions.dockerHost,
40
+ envFilePath: resolvedOptions.envFilePath
22
41
  });
42
+ let runMetadata;
23
43
  try {
24
- await (options.runRunner ?? runDockerContainer)(invocation);
44
+ runMetadata = await (options.runRunner ?? runDockerContainer)(invocation);
25
45
  }
26
46
  finally {
27
47
  if (!invocation.detach) {
28
48
  await removeDirectory(invocation.supportDirectory);
29
49
  }
30
50
  }
51
+ const deploymentRecordPath = invocation.detach && invocation.deploymentName
52
+ ? await writeDockerDeploymentRecordForRun({
53
+ authProfileName: authProfile?.name ?? null,
54
+ envFilePath: resolvedOptions.envFilePath,
55
+ imageTag,
56
+ invocation,
57
+ outputDirectory: buildResult.outputDirectory,
58
+ report: buildResult.report,
59
+ runMetadata: runMetadata ?? undefined,
60
+ targetExecFile: options.targetExecFile
61
+ })
62
+ : null;
31
63
  return {
32
64
  ...buildResult,
33
65
  authProfileName: authProfile?.name ?? null,
34
66
  containerName: invocation.containerName,
67
+ deploymentRecordPath,
68
+ imageTag,
35
69
  supportDirectory: invocation.supportDirectory
36
70
  };
37
71
  };
@@ -2,6 +2,7 @@ import path from "node:path";
2
2
  import { SpawnfileError } from "../../shared/index.js";
3
3
  import { buildCompilePlan } from "../buildCompilePlan.js";
4
4
  import { resolveMoltnetRoomMemberships } from "../moltnetRoomMemberships.js";
5
+ import { buildDeclaredNodeView, buildRuntimeViews } from "./declarations.js";
5
6
  const createNameCounts = (plan) => {
6
7
  const counts = new Map();
7
8
  for (const node of plan.nodes) {
@@ -82,7 +83,9 @@ const buildTreeNode = (node, nodeById, edgesBySource, nameCounts, ancestors = []
82
83
  kind: node.kind,
83
84
  name: node.value.name,
84
85
  networks: buildTreeNetworkSummaries(node),
86
+ declared: buildDeclaredNodeView(node),
85
87
  runtimeName: node.runtimeName,
88
+ slug: node.slug,
86
89
  source: node.value.source
87
90
  };
88
91
  };
@@ -219,6 +222,6 @@ export const buildOrganizationView = async (inputPath) => {
219
222
  networks: buildNetworks(plan),
220
223
  projectRoot: path.dirname(plan.root),
221
224
  root,
222
- runtimes: []
225
+ runtimes: buildRuntimeViews(plan)
223
226
  };
224
227
  };
@@ -0,0 +1,4 @@
1
+ import type { CompilePlan, CompilePlanNode } from "../types.js";
2
+ import type { OrganizationRuntimeView, OrganizationViewDeclaredSummary } from "./types.js";
3
+ export declare const buildDeclaredNodeView: (node: CompilePlanNode) => OrganizationViewDeclaredSummary;
4
+ export declare const buildRuntimeViews: (plan: CompilePlan) => OrganizationRuntimeView[];
@@ -0,0 +1,104 @@
1
+ import { resolveEffectiveModelTarget } from "../modelEnv.js";
2
+ import { listInteractiveSurfaceScopes } from "../interactiveSurfaceScopes.js";
3
+ const summarizeModel = (node) => {
4
+ if (node.value.kind !== "agent" || !node.value.execution?.model?.primary) {
5
+ return null;
6
+ }
7
+ const target = resolveEffectiveModelTarget(node.value.execution.model.primary, node.value.execution);
8
+ return {
9
+ authMethod: target.auth.method,
10
+ name: target.name,
11
+ provider: target.provider
12
+ };
13
+ };
14
+ const summarizeSchedule = (node) => {
15
+ if (node.value.kind !== "agent" || !node.value.schedule) {
16
+ return null;
17
+ }
18
+ const schedule = node.value.schedule;
19
+ if (schedule.kind === "cron") {
20
+ return {
21
+ expression: schedule.cron,
22
+ kind: schedule.kind,
23
+ ...(schedule.timezone ? { timezone: schedule.timezone } : {})
24
+ };
25
+ }
26
+ if (schedule.kind === "every") {
27
+ return {
28
+ expression: schedule.every,
29
+ kind: schedule.kind,
30
+ ...(schedule.timezone ? { timezone: schedule.timezone } : {})
31
+ };
32
+ }
33
+ return { kind: schedule.kind };
34
+ };
35
+ const surfaceSummaryFor = (name, surfaces) => {
36
+ if (!surfaces[name]) {
37
+ return null;
38
+ }
39
+ const scopes = name === "moltnet"
40
+ ? listInteractiveSurfaceScopes(surfaces).filter((scope) => scope.startsWith("moltnet:"))
41
+ : listInteractiveSurfaceScopes(surfaces).filter((scope) => scope === name);
42
+ return {
43
+ name,
44
+ scopes: scopes.length > 0 ? scopes : [name]
45
+ };
46
+ };
47
+ const summarizeSurfaces = (node) => {
48
+ const surfaces = node.value.kind === "agent" ? node.value.surfaces : undefined;
49
+ if (!surfaces) {
50
+ return [];
51
+ }
52
+ return ["discord", "http", "moltnet", "slack", "telegram", "webhook", "whatsapp"]
53
+ .map((name) => surfaceSummaryFor(name, surfaces))
54
+ .filter((surface) => surface !== null);
55
+ };
56
+ const summarizePackages = (node) => {
57
+ const packages = node.value.kind === "agent"
58
+ ? node.value.packages ?? []
59
+ : node.value.shared.packages ?? [];
60
+ return packages.map((pkg) => ({
61
+ id: pkg.id,
62
+ manager: pkg.manager,
63
+ name: pkg.name,
64
+ ...(pkg.scope ? { scope: pkg.scope } : {}),
65
+ ...(pkg.version ? { version: pkg.version } : {})
66
+ }));
67
+ };
68
+ const summarizeResources = (node) => (node.value.workspaceResources ?? []).map((resource) => ({
69
+ id: resource.id,
70
+ kind: resource.kind,
71
+ mode: resource.mode,
72
+ mount: resource.mount,
73
+ sharing: resource.sharing
74
+ }));
75
+ export const buildDeclaredNodeView = (node) => {
76
+ const skills = node.value.kind === "agent" ? node.value.skills : node.value.shared.skills;
77
+ const mcpServers = node.value.kind === "agent"
78
+ ? node.value.mcpServers
79
+ : node.value.shared.mcpServers;
80
+ return {
81
+ docs: node.value.docs.map((doc) => ({ role: doc.role, source: doc.sourcePath })),
82
+ mcpServers: mcpServers.map((server) => ({
83
+ name: server.name,
84
+ transport: server.transport
85
+ })),
86
+ model: summarizeModel(node),
87
+ packages: summarizePackages(node),
88
+ policy: {
89
+ mode: node.value.policyMode,
90
+ onDegrade: node.value.policyOnDegrade
91
+ },
92
+ resources: summarizeResources(node),
93
+ schedule: summarizeSchedule(node),
94
+ skills: skills.map((skill) => ({
95
+ name: skill.name,
96
+ ref: skill.ref,
97
+ requiresMcp: [...skill.requiresMcp]
98
+ })),
99
+ surfaces: summarizeSurfaces(node)
100
+ };
101
+ };
102
+ export const buildRuntimeViews = (plan) => Object.entries(plan.runtimes)
103
+ .map(([name, runtime]) => ({ name, nodeIds: [...runtime.nodeIds].sort() }))
104
+ .sort((left, right) => left.name.localeCompare(right.name));
@@ -8,8 +8,7 @@ const formatPolicy = (member) => {
8
8
  return [];
9
9
  }
10
10
  return [
11
- member.policy.read ? `read=${member.policy.read}` : undefined,
12
- member.policy.reply ? `reply=${member.policy.reply}` : undefined
11
+ member.policy.wake ? `wake=${member.policy.wake}` : undefined
13
12
  ].filter((entry) => entry !== undefined);
14
13
  };
15
14
  const formatMember = (member, options, projectRoot) => {
@@ -1,5 +1,9 @@
1
1
  import { formatSourceMeta } from "./sourcePaths.js";
2
2
  const color = (value, code, options) => options.color ? `\u001b[${code}m${value}\u001b[0m` : value;
3
+ const formatAnnotations = (subjectKey, options) => {
4
+ const annotations = options.annotationFor?.(subjectKey) ?? [];
5
+ return annotations.length > 0 ? ` ${annotations.join(" ")}` : "";
6
+ };
3
7
  const formatNode = (node, options, projectRoot) => {
4
8
  const kind = color(node.kind, node.kind === "team" ? "36" : "32", options);
5
9
  const metadata = node.kind === "team"
@@ -17,7 +21,7 @@ const formatNode = (node, options, projectRoot) => {
17
21
  const details = metadata.filter((entry) => entry !== undefined);
18
22
  const source = options.paths ? formatSourceMeta("source", node.source, projectRoot) : "";
19
23
  const separator = details[0]?.startsWith("[") ? " " : " ";
20
- return `${kind} ${node.displayName}${details.length > 0 ? `${separator}${details.join(" ")}` : ""}${source}`;
24
+ return `${kind} ${node.displayName}${details.length > 0 ? `${separator}${details.join(" ")}` : ""}${source}${formatAnnotations(node.id, options)}`;
21
25
  };
22
26
  const formatEdgeLabel = (edge) => edge.relation === "subagent"
23
27
  ? `subagent ${edge.label}`
@@ -44,7 +48,7 @@ const formatNetworkSummary = (network, options) => {
44
48
  return `${room.id}${roomMetadata.length > 0 ? ` ${roomMetadata.join(" ")}` : ""} [${room.declaredMembers.join(", ")}]`;
45
49
  })
46
50
  .join("; ");
47
- return `network ${id} "${network.name}"${metadata.length > 0 ? ` ${metadata.join(" ")}` : ""}: ${rooms}`;
51
+ return `network ${id} "${network.name}"${metadata.length > 0 ? ` ${metadata.join(" ")}` : ""}: ${rooms}${formatAnnotations(`network:${network.id}`, options)}`;
48
52
  };
49
53
  const renderChildren = (node, options, projectRoot, prefix = "") => {
50
54
  const glyphs = options.ascii
@@ -1,6 +1,63 @@
1
1
  import type { ResolvedMoltnetRoomPolicy } from "../types.js";
2
+ export interface OrganizationViewDocSummary {
3
+ role: string;
4
+ source: string;
5
+ }
6
+ export interface OrganizationViewSkillSummary {
7
+ name: string;
8
+ ref: string;
9
+ requiresMcp: string[];
10
+ }
11
+ export interface OrganizationViewResourceSummary {
12
+ id: string;
13
+ kind: "git" | "volume";
14
+ mode: "mutable" | "readonly";
15
+ mount: string;
16
+ sharing: "per_agent" | "team";
17
+ }
18
+ export interface OrganizationViewPackageSummary {
19
+ id: string;
20
+ manager: string;
21
+ name: string;
22
+ scope?: string;
23
+ version?: string;
24
+ }
25
+ export interface OrganizationViewMcpSummary {
26
+ name: string;
27
+ transport: "sse" | "stdio" | "streamable_http";
28
+ }
29
+ export interface OrganizationViewModelSummary {
30
+ authMethod: string;
31
+ name: string;
32
+ provider: string;
33
+ }
34
+ export interface OrganizationViewScheduleSummary {
35
+ expression?: string;
36
+ kind: "cron" | "disabled" | "every";
37
+ timezone?: string;
38
+ }
39
+ export interface OrganizationViewSurfaceSummary {
40
+ name: string;
41
+ scopes: string[];
42
+ }
43
+ export interface OrganizationViewPolicySummary {
44
+ mode: string | null;
45
+ onDegrade: string | null;
46
+ }
47
+ export interface OrganizationViewDeclaredSummary {
48
+ docs: OrganizationViewDocSummary[];
49
+ mcpServers: OrganizationViewMcpSummary[];
50
+ model: OrganizationViewModelSummary | null;
51
+ packages: OrganizationViewPackageSummary[];
52
+ policy: OrganizationViewPolicySummary;
53
+ resources: OrganizationViewResourceSummary[];
54
+ schedule: OrganizationViewScheduleSummary | null;
55
+ skills: OrganizationViewSkillSummary[];
56
+ surfaces: OrganizationViewSurfaceSummary[];
57
+ }
2
58
  export interface OrganizationViewTreeNode {
3
59
  children: OrganizationViewTreeEdge[];
60
+ declared?: OrganizationViewDeclaredSummary;
4
61
  displayName: string;
5
62
  external?: string[];
6
63
  id: string;
@@ -10,6 +67,7 @@ export interface OrganizationViewTreeNode {
10
67
  name: string;
11
68
  networks?: OrganizationTreeNetworkSummary[];
12
69
  runtimeName: string | null;
70
+ slug?: string;
13
71
  source: string;
14
72
  }
15
73
  export interface OrganizationTreeNetworkRoomSummary {
@@ -94,6 +152,10 @@ export interface OrganizationNetworkView {
94
152
  url?: string;
95
153
  declarations?: OrganizationNetworkDeclarationView[];
96
154
  }
155
+ export interface OrganizationRuntimeView {
156
+ name: string;
157
+ nodeIds: string[];
158
+ }
97
159
  export interface OrganizationView {
98
160
  contexts: [];
99
161
  diagnostics: [];
@@ -101,9 +163,10 @@ export interface OrganizationView {
101
163
  networks: OrganizationNetworkView[];
102
164
  projectRoot?: string;
103
165
  root: OrganizationViewTreeNode;
104
- runtimes: [];
166
+ runtimes: OrganizationRuntimeView[];
105
167
  }
106
168
  export interface RenderOrganizationViewOptions {
169
+ annotationFor?: (subjectKey: string) => string[];
107
170
  ascii?: boolean;
108
171
  color?: boolean;
109
172
  declared?: boolean;
@@ -0,0 +1,29 @@
1
+ import type { DeploymentRecord } from "./record.js";
2
+ export type DockerInspectExecFile = (file: string, args: string[], options: {
3
+ timeout: number;
4
+ }) => Promise<{
5
+ stderr: string;
6
+ stdout: string;
7
+ }>;
8
+ export interface DockerUnitInspection {
9
+ containerId: string | null;
10
+ drift: string[];
11
+ exists: boolean | null;
12
+ exitCode: number | null;
13
+ finishedAt: string | null;
14
+ imageId: string | null;
15
+ message: string;
16
+ restartCount: number | null;
17
+ running: boolean | null;
18
+ severity: "error" | "ok" | "unknown" | "warn";
19
+ startedAt: string | null;
20
+ status: string | null;
21
+ unitId: string;
22
+ }
23
+ export type DockerInspectionResult = Map<string, DockerUnitInspection>;
24
+ export interface DockerInspectOptions {
25
+ dockerCommand?: string;
26
+ execFile?: DockerInspectExecFile;
27
+ timeoutMs?: number;
28
+ }
29
+ export declare const inspectDockerDeployment: (record: DeploymentRecord, options?: DockerInspectOptions) => Promise<DockerInspectionResult>;
@@ -0,0 +1,130 @@
1
+ import { execFile as execFileCallback } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { dockerContextNameForTarget, verifyDockerDeploymentTarget } from "./target.js";
4
+ const execFile = promisify(execFileCallback);
5
+ const toStringOrNull = (value) => typeof value === "string" && value.length > 0 ? value : null;
6
+ const toNumberOrNull = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
7
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
8
+ const targetRefForUnit = (unit) => unit.container_id ?? unit.container_name;
9
+ const argsForInspect = (record, targetRef) => {
10
+ const context = dockerContextNameForTarget(record.target);
11
+ if (context) {
12
+ return ["--context", context, "inspect", targetRef];
13
+ }
14
+ if (record.target.kind === "host") {
15
+ return ["--host", record.target.value, "inspect", targetRef];
16
+ }
17
+ return ["inspect", targetRef];
18
+ };
19
+ const matchesDockerId = (recorded, live) => !recorded
20
+ || !live
21
+ || recorded === live
22
+ || live.startsWith(recorded)
23
+ || recorded.startsWith(live);
24
+ const createDriftMessages = (unit, inspected) => {
25
+ const containerId = toStringOrNull(inspected.Id);
26
+ const imageId = toStringOrNull(inspected.Image);
27
+ const drift = [];
28
+ if (!matchesDockerId(unit.container_id, containerId)) {
29
+ drift.push(`container id drift: recorded ${unit.container_id}, live ${containerId}`);
30
+ }
31
+ if (!matchesDockerId(unit.image_id, imageId)) {
32
+ drift.push(`image id drift: recorded ${unit.image_id}, live ${imageId}`);
33
+ }
34
+ return { containerId, drift, imageId };
35
+ };
36
+ const inspectionFromState = (unitId, unit, inspected, state) => {
37
+ const running = typeof state.Running === "boolean" ? state.Running : null;
38
+ const status = toStringOrNull(state.Status);
39
+ const { containerId, drift, imageId } = createDriftMessages(unit, inspected);
40
+ const baseMessage = running === true
41
+ ? `container is running (${status ?? "running"})`
42
+ : `container is not running (${status ?? "unknown"})`;
43
+ return {
44
+ containerId,
45
+ drift,
46
+ exists: true,
47
+ exitCode: toNumberOrNull(state.ExitCode),
48
+ finishedAt: toStringOrNull(state.FinishedAt),
49
+ imageId,
50
+ message: drift.length > 0 ? `${baseMessage}; ${drift.join("; ")}` : baseMessage,
51
+ restartCount: toNumberOrNull(state.RestartCount),
52
+ running,
53
+ severity: drift.length > 0 ? "warn" : running === false ? "error" : "ok",
54
+ startedAt: toStringOrNull(state.StartedAt),
55
+ status,
56
+ unitId
57
+ };
58
+ };
59
+ const missingInspection = (unitId, message) => ({
60
+ containerId: null,
61
+ drift: [],
62
+ exists: false,
63
+ exitCode: null,
64
+ finishedAt: null,
65
+ imageId: null,
66
+ message,
67
+ restartCount: null,
68
+ running: false,
69
+ severity: "warn",
70
+ startedAt: null,
71
+ status: null,
72
+ unitId
73
+ });
74
+ const unknownInspection = (unitId, message, severity = "unknown") => ({
75
+ containerId: null,
76
+ drift: [],
77
+ exists: null,
78
+ exitCode: null,
79
+ finishedAt: null,
80
+ imageId: null,
81
+ message,
82
+ restartCount: null,
83
+ running: null,
84
+ severity,
85
+ startedAt: null,
86
+ status: null,
87
+ unitId
88
+ });
89
+ const inspectUnit = async (record, unit, options) => {
90
+ const targetRef = targetRefForUnit(unit);
91
+ if (!targetRef) {
92
+ return unknownInspection(unit.id, "deployment unit has no recorded container id or name");
93
+ }
94
+ try {
95
+ const { stdout } = await options.execFile(options.dockerCommand, argsForInspect(record, targetRef), { timeout: options.timeoutMs });
96
+ const parsed = JSON.parse(stdout);
97
+ const [first] = Array.isArray(parsed) ? parsed : [];
98
+ const state = isRecord(first) && isRecord(first.State) ? first.State : null;
99
+ return state
100
+ ? inspectionFromState(unit.id, unit, first, state)
101
+ : unknownInspection(unit.id, `docker inspect returned no state for ${targetRef}`);
102
+ }
103
+ catch (error) {
104
+ const stderr = isRecord(error) ? toStringOrNull(error.stderr) : null;
105
+ const message = stderr ?? (error instanceof Error ? error.message : String(error));
106
+ if (/No such object/i.test(message)) {
107
+ return missingInspection(unit.id, `recorded container ${targetRef} is missing`);
108
+ }
109
+ return unknownInspection(unit.id, `unable to inspect container ${targetRef}: ${message}`, "error");
110
+ }
111
+ };
112
+ export const inspectDockerDeployment = async (record, options = {}) => {
113
+ const resolvedOptions = {
114
+ dockerCommand: options.dockerCommand ?? "docker",
115
+ execFile: options.execFile ?? execFile,
116
+ timeoutMs: options.timeoutMs ?? 10_000
117
+ };
118
+ try {
119
+ await verifyDockerDeploymentTarget(record.target, resolvedOptions);
120
+ }
121
+ catch (error) {
122
+ const message = error instanceof Error ? error.message : String(error);
123
+ return new Map(record.units.map((unit) => [
124
+ unit.id,
125
+ unknownInspection(unit.id, `unable to verify recorded Docker target: ${message}`, "error")
126
+ ]));
127
+ }
128
+ const inspections = await Promise.all(record.units.map(async (unit) => inspectUnit(record, unit, resolvedOptions)));
129
+ return new Map(inspections.map((inspection) => [inspection.unitId, inspection]));
130
+ };
@@ -0,0 +1,16 @@
1
+ export declare const dockerDeploymentLabelKeys: {
2
+ readonly compileFingerprint: "com.spawnfile.compile_fingerprint";
3
+ readonly deployment: "com.spawnfile.deployment";
4
+ readonly project: "com.spawnfile.project";
5
+ readonly unit: "com.spawnfile.unit";
6
+ readonly version: "com.spawnfile.version";
7
+ };
8
+ export interface DockerDeploymentLabelInput {
9
+ compileFingerprint: string;
10
+ deployment: string;
11
+ project: string;
12
+ unit: string;
13
+ version: string;
14
+ }
15
+ export declare const createDockerDeploymentLabels: (input: DockerDeploymentLabelInput) => Record<string, string>;
16
+ export declare const appendDockerLabelArgs: (args: string[], labels: Record<string, string>) => void;
@@ -0,0 +1,32 @@
1
+ import { SpawnfileError } from "../shared/index.js";
2
+ export const dockerDeploymentLabelKeys = {
3
+ compileFingerprint: "com.spawnfile.compile_fingerprint",
4
+ deployment: "com.spawnfile.deployment",
5
+ project: "com.spawnfile.project",
6
+ unit: "com.spawnfile.unit",
7
+ version: "com.spawnfile.version"
8
+ };
9
+ const dockerLabelValuePattern = /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/;
10
+ const assertIdentifierLabelValue = (key, value) => {
11
+ if (!dockerLabelValuePattern.test(value)) {
12
+ throw new SpawnfileError("validation_error", `Docker deployment label ${key} must be an identifier`);
13
+ }
14
+ };
15
+ export const createDockerDeploymentLabels = (input) => {
16
+ const labels = {
17
+ [dockerDeploymentLabelKeys.compileFingerprint]: input.compileFingerprint,
18
+ [dockerDeploymentLabelKeys.deployment]: input.deployment,
19
+ [dockerDeploymentLabelKeys.project]: input.project,
20
+ [dockerDeploymentLabelKeys.unit]: input.unit,
21
+ [dockerDeploymentLabelKeys.version]: input.version
22
+ };
23
+ for (const [key, value] of Object.entries(labels)) {
24
+ assertIdentifierLabelValue(key, value);
25
+ }
26
+ return labels;
27
+ };
28
+ export const appendDockerLabelArgs = (args, labels) => {
29
+ for (const [key, value] of Object.entries(labels).sort(([left], [right]) => left.localeCompare(right))) {
30
+ args.push("--label", `${key}=${value}`);
31
+ }
32
+ };