spawnfile 0.1.0

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 (177) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +464 -0
  3. package/dist/.env.example +5 -0
  4. package/dist/Dockerfile +21 -0
  5. package/dist/auth/importers.d.ts +8 -0
  6. package/dist/auth/importers.js +93 -0
  7. package/dist/auth/index.d.ts +5 -0
  8. package/dist/auth/index.js +5 -0
  9. package/dist/auth/paths.d.ts +6 -0
  10. package/dist/auth/paths.js +18 -0
  11. package/dist/auth/profileStore.d.ts +10 -0
  12. package/dist/auth/profileStore.js +125 -0
  13. package/dist/auth/runtimeCredentials.d.ts +14 -0
  14. package/dist/auth/runtimeCredentials.js +76 -0
  15. package/dist/auth/types.d.ts +22 -0
  16. package/dist/auth/types.js +1 -0
  17. package/dist/cli/index.d.ts +2 -0
  18. package/dist/cli/index.js +4 -0
  19. package/dist/cli/runCli.d.ts +27 -0
  20. package/dist/cli/runCli.js +314 -0
  21. package/dist/compiler/addProjectNode.d.ts +21 -0
  22. package/dist/compiler/addProjectNode.js +126 -0
  23. package/dist/compiler/agentSurfaces.d.ts +4 -0
  24. package/dist/compiler/agentSurfaces.js +70 -0
  25. package/dist/compiler/buildCompilePlan.d.ts +2 -0
  26. package/dist/compiler/buildCompilePlan.js +258 -0
  27. package/dist/compiler/buildProject.d.ts +20 -0
  28. package/dist/compiler/buildProject.js +52 -0
  29. package/dist/compiler/compilePlanHelpers.d.ts +7 -0
  30. package/dist/compiler/compilePlanHelpers.js +39 -0
  31. package/dist/compiler/compileProject.d.ts +11 -0
  32. package/dist/compiler/compileProject.js +182 -0
  33. package/dist/compiler/containerArtifacts.d.ts +4 -0
  34. package/dist/compiler/containerArtifacts.js +64 -0
  35. package/dist/compiler/containerArtifactsPlans.d.ts +4 -0
  36. package/dist/compiler/containerArtifactsPlans.js +154 -0
  37. package/dist/compiler/containerArtifactsRender.d.ts +6 -0
  38. package/dist/compiler/containerArtifactsRender.js +237 -0
  39. package/dist/compiler/containerArtifactsTypes.d.ts +42 -0
  40. package/dist/compiler/containerArtifactsTypes.js +1 -0
  41. package/dist/compiler/discordSurface.d.ts +4 -0
  42. package/dist/compiler/discordSurface.js +28 -0
  43. package/dist/compiler/executionDefaults.d.ts +2 -0
  44. package/dist/compiler/executionDefaults.js +9 -0
  45. package/dist/compiler/helpers.d.ts +7 -0
  46. package/dist/compiler/helpers.js +35 -0
  47. package/dist/compiler/index.d.ts +9 -0
  48. package/dist/compiler/index.js +9 -0
  49. package/dist/compiler/initProject.d.ts +9 -0
  50. package/dist/compiler/initProject.js +46 -0
  51. package/dist/compiler/modelAuth.d.ts +2 -0
  52. package/dist/compiler/modelAuth.js +17 -0
  53. package/dist/compiler/modelEnv.d.ts +10 -0
  54. package/dist/compiler/modelEnv.js +97 -0
  55. package/dist/compiler/runProject.d.ts +34 -0
  56. package/dist/compiler/runProject.js +197 -0
  57. package/dist/compiler/runProjectAuth.d.ts +9 -0
  58. package/dist/compiler/runProjectAuth.js +59 -0
  59. package/dist/compiler/surfaceSupport.d.ts +2 -0
  60. package/dist/compiler/surfaceSupport.js +13 -0
  61. package/dist/compiler/surfaces.d.ts +21 -0
  62. package/dist/compiler/surfaces.js +59 -0
  63. package/dist/compiler/syncProjectAuth.d.ts +7 -0
  64. package/dist/compiler/syncProjectAuth.js +65 -0
  65. package/dist/compiler/types.d.ts +134 -0
  66. package/dist/compiler/types.js +1 -0
  67. package/dist/compiler/updateProjectModels.d.ts +20 -0
  68. package/dist/compiler/updateProjectModels.js +181 -0
  69. package/dist/container/rootfs/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/config.json +16 -0
  70. package/dist/container/rootfs/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/workspace/AGENTS.md +1 -0
  71. package/dist/e2e/cli.d.ts +1 -0
  72. package/dist/e2e/cli.js +40 -0
  73. package/dist/e2e/dockerAuth.d.ts +18 -0
  74. package/dist/e2e/dockerAuth.js +212 -0
  75. package/dist/e2e/fixtures.d.ts +2 -0
  76. package/dist/e2e/fixtures.js +49 -0
  77. package/dist/e2e/index.d.ts +4 -0
  78. package/dist/e2e/index.js +4 -0
  79. package/dist/e2e/runtimePrompts.d.ts +13 -0
  80. package/dist/e2e/runtimePrompts.js +132 -0
  81. package/dist/e2e/scenarios.d.ts +3 -0
  82. package/dist/e2e/scenarios.js +84 -0
  83. package/dist/e2e/types.d.ts +35 -0
  84. package/dist/e2e/types.js +1 -0
  85. package/dist/entrypoint.sh +71 -0
  86. package/dist/filesystem/index.d.ts +2 -0
  87. package/dist/filesystem/index.js +2 -0
  88. package/dist/filesystem/io.d.ts +11 -0
  89. package/dist/filesystem/io.js +57 -0
  90. package/dist/filesystem/paths.d.ts +6 -0
  91. package/dist/filesystem/paths.js +30 -0
  92. package/dist/manifest/index.d.ts +5 -0
  93. package/dist/manifest/index.js +5 -0
  94. package/dist/manifest/loadManifest.d.ts +12 -0
  95. package/dist/manifest/loadManifest.js +208 -0
  96. package/dist/manifest/renderSpawnfile.d.ts +2 -0
  97. package/dist/manifest/renderSpawnfile.js +211 -0
  98. package/dist/manifest/scaffold.d.ts +16 -0
  99. package/dist/manifest/scaffold.js +41 -0
  100. package/dist/manifest/schemas.d.ts +989 -0
  101. package/dist/manifest/schemas.js +314 -0
  102. package/dist/manifest/skillFrontmatter.d.ts +5 -0
  103. package/dist/manifest/skillFrontmatter.js +32 -0
  104. package/dist/manifest/surfaceSchemas.d.ts +148 -0
  105. package/dist/manifest/surfaceSchemas.js +162 -0
  106. package/dist/report/createDiagnostic.d.ts +2 -0
  107. package/dist/report/createDiagnostic.js +4 -0
  108. package/dist/report/createReport.d.ts +2 -0
  109. package/dist/report/createReport.js +7 -0
  110. package/dist/report/index.d.ts +4 -0
  111. package/dist/report/index.js +4 -0
  112. package/dist/report/types.d.ts +50 -0
  113. package/dist/report/types.js +1 -0
  114. package/dist/report/writeReport.d.ts +2 -0
  115. package/dist/report/writeReport.js +9 -0
  116. package/dist/runtime/common.d.ts +13 -0
  117. package/dist/runtime/common.js +63 -0
  118. package/dist/runtime/container.d.ts +8 -0
  119. package/dist/runtime/container.js +67 -0
  120. package/dist/runtime/index.d.ts +4 -0
  121. package/dist/runtime/index.js +4 -0
  122. package/dist/runtime/install.d.ts +51 -0
  123. package/dist/runtime/install.js +167 -0
  124. package/dist/runtime/openclaw/adapter.d.ts +2 -0
  125. package/dist/runtime/openclaw/adapter.js +194 -0
  126. package/dist/runtime/openclaw/runAuth.d.ts +2 -0
  127. package/dist/runtime/openclaw/runAuth.js +125 -0
  128. package/dist/runtime/openclaw/scaffold-assets/AGENTS.md +120 -0
  129. package/dist/runtime/openclaw/scaffold-assets/CLAUDE.md +5 -0
  130. package/dist/runtime/openclaw/scaffold-assets/IDENTITY.md +23 -0
  131. package/dist/runtime/openclaw/scaffold-assets/SOUL.md +36 -0
  132. package/dist/runtime/openclaw/scaffold.d.ts +2 -0
  133. package/dist/runtime/openclaw/scaffold.js +28 -0
  134. package/dist/runtime/openclaw/surfaces.d.ts +5 -0
  135. package/dist/runtime/openclaw/surfaces.js +253 -0
  136. package/dist/runtime/picoclaw/adapter.d.ts +2 -0
  137. package/dist/runtime/picoclaw/adapter.js +204 -0
  138. package/dist/runtime/picoclaw/runAuth.d.ts +2 -0
  139. package/dist/runtime/picoclaw/runAuth.js +117 -0
  140. package/dist/runtime/picoclaw/scaffold-assets/AGENTS.md +3 -0
  141. package/dist/runtime/picoclaw/scaffold-assets/CLAUDE.md +5 -0
  142. package/dist/runtime/picoclaw/scaffold-assets/IDENTITY.md +3 -0
  143. package/dist/runtime/picoclaw/scaffold-assets/SOUL.md +3 -0
  144. package/dist/runtime/picoclaw/scaffold.d.ts +2 -0
  145. package/dist/runtime/picoclaw/scaffold.js +28 -0
  146. package/dist/runtime/picoclaw/surfaces.d.ts +5 -0
  147. package/dist/runtime/picoclaw/surfaces.js +111 -0
  148. package/dist/runtime/registry.d.ts +41 -0
  149. package/dist/runtime/registry.js +134 -0
  150. package/dist/runtime/scaffoldAssets.d.ts +1 -0
  151. package/dist/runtime/scaffoldAssets.js +2 -0
  152. package/dist/runtime/tinyclaw/adapter.d.ts +2 -0
  153. package/dist/runtime/tinyclaw/adapter.js +263 -0
  154. package/dist/runtime/tinyclaw/runAuth.d.ts +2 -0
  155. package/dist/runtime/tinyclaw/runAuth.js +22 -0
  156. package/dist/runtime/tinyclaw/scaffold-assets/AGENTS.md +160 -0
  157. package/dist/runtime/tinyclaw/scaffold-assets/CLAUDE.md +5 -0
  158. package/dist/runtime/tinyclaw/scaffold-assets/SOUL.md +177 -0
  159. package/dist/runtime/tinyclaw/scaffold.d.ts +2 -0
  160. package/dist/runtime/tinyclaw/scaffold.js +24 -0
  161. package/dist/runtime/tinyclaw/surfaces.d.ts +8 -0
  162. package/dist/runtime/tinyclaw/surfaces.js +86 -0
  163. package/dist/runtime/types.d.ts +87 -0
  164. package/dist/runtime/types.js +1 -0
  165. package/dist/runtimes/picoclaw/agents/assistant/config.json +16 -0
  166. package/dist/runtimes/picoclaw/agents/assistant/workspace/AGENTS.md +1 -0
  167. package/dist/shared/constants.d.ts +7 -0
  168. package/dist/shared/constants.js +7 -0
  169. package/dist/shared/errors.d.ts +6 -0
  170. package/dist/shared/errors.js +9 -0
  171. package/dist/shared/index.d.ts +3 -0
  172. package/dist/shared/index.js +3 -0
  173. package/dist/shared/types.d.ts +9 -0
  174. package/dist/shared/types.js +1 -0
  175. package/dist/spawnfile-report.json +71 -0
  176. package/package.json +41 -0
  177. package/runtimes.yaml +62 -0
@@ -0,0 +1,126 @@
1
+ import path from "node:path";
2
+ import { fileExists, getManifestPath, getProjectRoot, writeUtf8File } from "../filesystem/index.js";
3
+ import { isAgentManifest, isTeamManifest, loadManifest, renderSpawnfile } from "../manifest/index.js";
4
+ import { SpawnfileError } from "../shared/index.js";
5
+ import { initProject } from "./initProject.js";
6
+ const assertAddableId = (id) => {
7
+ if (id.trim().length === 0) {
8
+ throw new SpawnfileError("validation_error", "Child id must not be empty");
9
+ }
10
+ if (/\s/.test(id)) {
11
+ throw new SpawnfileError("validation_error", `Child id must not contain whitespace: ${id}`);
12
+ }
13
+ if (/[\\/]/.test(id)) {
14
+ throw new SpawnfileError("validation_error", `Child id must not contain path separators: ${id}`);
15
+ }
16
+ };
17
+ const resolveTargetManifestPath = (inputPath) => getManifestPath(path.resolve(inputPath ?? process.cwd()));
18
+ const assertChildDirectoryAvailable = async (childDirectory) => {
19
+ if (await fileExists(childDirectory)) {
20
+ throw new SpawnfileError("io_error", `Refusing to overwrite existing child directory at ${childDirectory}`);
21
+ }
22
+ };
23
+ const assertUniqueRef = (refs, id, label) => {
24
+ if (refs.some((entry) => entry.id === id)) {
25
+ throw new SpawnfileError("validation_error", `Duplicate ${label} id: ${id}`);
26
+ }
27
+ };
28
+ const updateParentManifest = async (manifestPath, manifest) => {
29
+ await writeUtf8File(manifestPath, renderSpawnfile(manifest));
30
+ };
31
+ const getRuntimeName = (runtime) => typeof runtime === "string" ? runtime : runtime?.name;
32
+ const createTeamMember = (id, relativeRef) => ({
33
+ id,
34
+ ref: relativeRef
35
+ });
36
+ export const addAgentProject = async (options) => {
37
+ assertAddableId(options.id);
38
+ const manifestPath = resolveTargetManifestPath(options.path);
39
+ const loadedManifest = await loadManifest(manifestPath);
40
+ if (!isTeamManifest(loadedManifest.manifest)) {
41
+ throw new SpawnfileError("validation_error", "spawnfile add agent only works on team projects");
42
+ }
43
+ assertUniqueRef(loadedManifest.manifest.members, options.id, "member");
44
+ const targetDirectory = getProjectRoot(manifestPath);
45
+ const childDirectory = path.join(targetDirectory, "agents", options.id);
46
+ await assertChildDirectoryAvailable(childDirectory);
47
+ const child = await initProject({
48
+ directory: childDirectory,
49
+ runtime: options.runtime
50
+ });
51
+ await updateParentManifest(manifestPath, {
52
+ ...loadedManifest.manifest,
53
+ members: [
54
+ ...loadedManifest.manifest.members,
55
+ createTeamMember(options.id, `./agents/${options.id}`)
56
+ ]
57
+ });
58
+ return {
59
+ createdFiles: child.createdFiles,
60
+ targetDirectory,
61
+ updatedFiles: [manifestPath]
62
+ };
63
+ };
64
+ export const addSubagentProject = async (options) => {
65
+ assertAddableId(options.id);
66
+ const manifestPath = resolveTargetManifestPath(options.path);
67
+ const loadedManifest = await loadManifest(manifestPath);
68
+ if (!isAgentManifest(loadedManifest.manifest)) {
69
+ throw new SpawnfileError("validation_error", "spawnfile add subagent only works on agent projects");
70
+ }
71
+ assertUniqueRef(loadedManifest.manifest.subagents ?? [], options.id, "subagent");
72
+ const runtimeName = getRuntimeName(loadedManifest.manifest.runtime);
73
+ if (!runtimeName) {
74
+ throw new SpawnfileError("validation_error", "Agent must declare a runtime before adding subagents");
75
+ }
76
+ const targetDirectory = getProjectRoot(manifestPath);
77
+ const childDirectory = path.join(targetDirectory, "subagents", options.id);
78
+ await assertChildDirectoryAvailable(childDirectory);
79
+ const child = await initProject({
80
+ directory: childDirectory,
81
+ runtime: runtimeName
82
+ });
83
+ await updateParentManifest(manifestPath, {
84
+ ...loadedManifest.manifest,
85
+ subagents: [
86
+ ...(loadedManifest.manifest.subagents ?? []),
87
+ {
88
+ id: options.id,
89
+ ref: `./subagents/${options.id}`
90
+ }
91
+ ]
92
+ });
93
+ return {
94
+ createdFiles: child.createdFiles,
95
+ targetDirectory,
96
+ updatedFiles: [manifestPath]
97
+ };
98
+ };
99
+ export const addTeamProject = async (options) => {
100
+ assertAddableId(options.id);
101
+ const manifestPath = resolveTargetManifestPath(options.path);
102
+ const loadedManifest = await loadManifest(manifestPath);
103
+ if (!isTeamManifest(loadedManifest.manifest)) {
104
+ throw new SpawnfileError("validation_error", "spawnfile add team only works on team projects");
105
+ }
106
+ assertUniqueRef(loadedManifest.manifest.members, options.id, "member");
107
+ const targetDirectory = getProjectRoot(manifestPath);
108
+ const childDirectory = path.join(targetDirectory, "teams", options.id);
109
+ await assertChildDirectoryAvailable(childDirectory);
110
+ const child = await initProject({
111
+ directory: childDirectory,
112
+ team: true
113
+ });
114
+ await updateParentManifest(manifestPath, {
115
+ ...loadedManifest.manifest,
116
+ members: [
117
+ ...loadedManifest.manifest.members,
118
+ createTeamMember(options.id, `./teams/${options.id}`)
119
+ ]
120
+ });
121
+ return {
122
+ createdFiles: child.createdFiles,
123
+ targetDirectory,
124
+ updatedFiles: [manifestPath]
125
+ };
126
+ };
@@ -0,0 +1,4 @@
1
+ import { SurfacesBlock } from "../manifest/index.js";
2
+ import type { ResolvedAgentSurfaces } from "./types.js";
3
+ export declare const resolveAgentSurfaces: (surfaces: SurfacesBlock | undefined) => ResolvedAgentSurfaces | undefined;
4
+ export declare const listAgentSurfaceSecretNames: (surfaces: ResolvedAgentSurfaces | undefined) => string[];
@@ -0,0 +1,70 @@
1
+ import { DEFAULT_DISCORD_BOT_TOKEN_SECRET, DEFAULT_SLACK_APP_TOKEN_SECRET, DEFAULT_SLACK_BOT_TOKEN_SECRET, DEFAULT_TELEGRAM_BOT_TOKEN_SECRET } from "../shared/index.js";
2
+ export const resolveAgentSurfaces = (surfaces) => {
3
+ if (!surfaces) {
4
+ return undefined;
5
+ }
6
+ const resolved = {};
7
+ if (surfaces.discord) {
8
+ resolved.discord = {
9
+ ...(surfaces.discord.access
10
+ ? {
11
+ access: {
12
+ channels: [...(surfaces.discord.access.channels ?? [])],
13
+ guilds: [...(surfaces.discord.access.guilds ?? [])],
14
+ mode: surfaces.discord.access.mode ?? "allowlist",
15
+ users: [...(surfaces.discord.access.users ?? [])]
16
+ }
17
+ }
18
+ : {}),
19
+ botTokenSecret: surfaces.discord.bot_token_secret ?? DEFAULT_DISCORD_BOT_TOKEN_SECRET
20
+ };
21
+ }
22
+ if (surfaces.telegram) {
23
+ resolved.telegram = {
24
+ ...(surfaces.telegram.access
25
+ ? {
26
+ access: {
27
+ chats: [...(surfaces.telegram.access.chats ?? [])],
28
+ mode: surfaces.telegram.access.mode ?? "allowlist",
29
+ users: [...(surfaces.telegram.access.users ?? [])]
30
+ }
31
+ }
32
+ : {}),
33
+ botTokenSecret: surfaces.telegram.bot_token_secret ?? DEFAULT_TELEGRAM_BOT_TOKEN_SECRET
34
+ };
35
+ }
36
+ if (surfaces.whatsapp) {
37
+ resolved.whatsapp = surfaces.whatsapp.access
38
+ ? {
39
+ access: {
40
+ groups: [...(surfaces.whatsapp.access.groups ?? [])],
41
+ mode: surfaces.whatsapp.access.mode ?? "allowlist",
42
+ users: [...(surfaces.whatsapp.access.users ?? [])]
43
+ }
44
+ }
45
+ : {};
46
+ }
47
+ if (surfaces.slack) {
48
+ resolved.slack = {
49
+ ...(surfaces.slack.access
50
+ ? {
51
+ access: {
52
+ channels: [...(surfaces.slack.access.channels ?? [])],
53
+ mode: surfaces.slack.access.mode ?? "allowlist",
54
+ users: [...(surfaces.slack.access.users ?? [])]
55
+ }
56
+ }
57
+ : {}),
58
+ appTokenSecret: surfaces.slack.app_token_secret ?? DEFAULT_SLACK_APP_TOKEN_SECRET,
59
+ botTokenSecret: surfaces.slack.bot_token_secret ?? DEFAULT_SLACK_BOT_TOKEN_SECRET
60
+ };
61
+ }
62
+ return Object.keys(resolved).length > 0 ? resolved : undefined;
63
+ };
64
+ export const listAgentSurfaceSecretNames = (surfaces) => [
65
+ ...(surfaces?.discord ? [surfaces.discord.botTokenSecret] : []),
66
+ ...(surfaces?.slack
67
+ ? [surfaces.slack.appTokenSecret, surfaces.slack.botTokenSecret]
68
+ : []),
69
+ ...(surfaces?.telegram ? [surfaces.telegram.botTokenSecret] : [])
70
+ ].sort();
@@ -0,0 +1,2 @@
1
+ import { CompilePlan } from "./types.js";
2
+ export declare const buildCompilePlan: (inputPath: string) => Promise<CompilePlan>;
@@ -0,0 +1,258 @@
1
+ import { getCanonicalManifestPath, getManifestPath, resolveProjectPath } from "../filesystem/index.js";
2
+ import { isAgentManifest, isTeamManifest, loadManifest, mergeExecution, normalizeRuntimeBinding } from "../manifest/index.js";
3
+ import { assertRuntimeCanCompile } from "../runtime/index.js";
4
+ import { SpawnfileError } from "../shared/index.js";
5
+ import { getAgentFingerprint, getMcpNames, getTeamFingerprint, validateEffectiveSkillRequirements } from "./compilePlanHelpers.js";
6
+ import { resolveAgentSurfaces } from "./agentSurfaces.js";
7
+ import { assignStableNodeIds } from "./helpers.js";
8
+ import { loadResolvedDocuments, mergeResolvedSkills, loadResolvedSkills, mergeSharedSurface } from "./surfaces.js";
9
+ import { assertRuntimeSupportsExecutionModelAuth } from "./modelAuth.js";
10
+ import { assertRuntimeSupportsAgentSurfaces } from "./surfaceSupport.js";
11
+ import { applyExecutionDefaults } from "./executionDefaults.js";
12
+ const resolveRuntime = async (manifest, context) => {
13
+ const localRuntime = normalizeRuntimeBinding(manifest.runtime);
14
+ if (context.isSubagent) {
15
+ if (!context.inheritedRuntime) {
16
+ throw new SpawnfileError("runtime_error", `Subagent ${manifest.name} is missing inherited runtime context`);
17
+ }
18
+ if (localRuntime &&
19
+ localRuntime.name !== context.inheritedRuntime.name) {
20
+ throw new SpawnfileError("runtime_error", `Subagent ${manifest.name} must match parent runtime`);
21
+ }
22
+ return context.inheritedRuntime;
23
+ }
24
+ if (!localRuntime) {
25
+ throw new SpawnfileError("runtime_error", `Agent ${manifest.name} does not declare a runtime`);
26
+ }
27
+ await assertRuntimeCanCompile(localRuntime.name);
28
+ return localRuntime;
29
+ };
30
+ export const buildCompilePlan = async (inputPath) => {
31
+ const rootManifestPath = getCanonicalManifestPath(getManifestPath(inputPath));
32
+ const loadCache = new Map();
33
+ const nodeCache = new Map();
34
+ const fingerprintCache = new Map();
35
+ const edges = [];
36
+ const visitStack = [];
37
+ const getLoadedManifest = (manifestPath) => {
38
+ const canonicalPath = getCanonicalManifestPath(manifestPath);
39
+ const cached = loadCache.get(canonicalPath);
40
+ if (cached) {
41
+ return cached;
42
+ }
43
+ const promise = loadManifest(canonicalPath);
44
+ loadCache.set(canonicalPath, promise);
45
+ return promise;
46
+ };
47
+ const visitAgent = async (manifestPath, context) => {
48
+ const canonicalPath = getCanonicalManifestPath(manifestPath);
49
+ if (visitStack.includes(canonicalPath)) {
50
+ throw new SpawnfileError("compile_error", `Cycle detected while visiting ${canonicalPath}`);
51
+ }
52
+ const loadedManifest = await getLoadedManifest(canonicalPath);
53
+ if (!isAgentManifest(loadedManifest.manifest)) {
54
+ throw new SpawnfileError("compile_error", `Expected agent manifest, got ${loadedManifest.manifest.kind} at ${canonicalPath}`);
55
+ }
56
+ const runtime = await resolveRuntime(loadedManifest.manifest, context);
57
+ const execution = applyExecutionDefaults(context.isSubagent
58
+ ? mergeExecution(context.inheritedExecution, loadedManifest.manifest.execution)
59
+ : loadedManifest.manifest.execution);
60
+ assertRuntimeSupportsExecutionModelAuth(runtime.name, execution, loadedManifest.manifest.name);
61
+ const sharedSurface = mergeSharedSurface(context.inheritedShared?.surface, {
62
+ env: loadedManifest.manifest.env,
63
+ mcpServers: loadedManifest.manifest.mcp_servers,
64
+ secrets: loadedManifest.manifest.secrets,
65
+ skills: loadedManifest.manifest.skills
66
+ });
67
+ const inheritedSkills = context.inheritedShared
68
+ ? await loadResolvedSkills(context.inheritedShared.manifestPath, context.inheritedShared.surface?.skills)
69
+ : [];
70
+ const localSkills = await loadResolvedSkills(canonicalPath, loadedManifest.manifest.skills);
71
+ const skills = mergeResolvedSkills(inheritedSkills, localSkills);
72
+ validateEffectiveSkillRequirements(loadedManifest.manifest.name, getMcpNames(sharedSurface.mcpServers), skills);
73
+ const candidate = {
74
+ docs: await loadResolvedDocuments(canonicalPath, loadedManifest.manifest.docs),
75
+ env: sharedSurface.env,
76
+ execution,
77
+ kind: "agent",
78
+ mcpServers: sharedSurface.mcpServers,
79
+ name: loadedManifest.manifest.name,
80
+ policyMode: loadedManifest.manifest.policy?.mode ?? null,
81
+ policyOnDegrade: loadedManifest.manifest.policy?.on_degrade ?? null,
82
+ runtime,
83
+ secrets: sharedSurface.secrets,
84
+ skills,
85
+ source: canonicalPath,
86
+ surfaces: resolveAgentSurfaces(loadedManifest.manifest.surfaces),
87
+ subagents: []
88
+ };
89
+ assertRuntimeSupportsAgentSurfaces(runtime.name, candidate.surfaces, loadedManifest.manifest.name);
90
+ const fingerprint = getAgentFingerprint(candidate);
91
+ const existingFingerprint = fingerprintCache.get(canonicalPath);
92
+ if (existingFingerprint && existingFingerprint !== fingerprint) {
93
+ throw new SpawnfileError("compile_error", `Manifest ${canonicalPath} resolves differently across compile contexts`);
94
+ }
95
+ const cachedNode = nodeCache.get(canonicalPath);
96
+ if (cachedNode) {
97
+ return cachedNode.value;
98
+ }
99
+ fingerprintCache.set(canonicalPath, fingerprint);
100
+ nodeCache.set(canonicalPath, {
101
+ runtimeName: runtime.name,
102
+ source: canonicalPath,
103
+ value: candidate
104
+ });
105
+ visitStack.push(canonicalPath);
106
+ for (const subagent of loadedManifest.manifest.subagents ?? []) {
107
+ const childManifestPath = getManifestPath(resolveProjectPath(canonicalPath, subagent.ref));
108
+ const resolvedSubagent = await visitAgent(childManifestPath, {
109
+ inheritedExecution: execution,
110
+ inheritedRuntime: runtime,
111
+ isSubagent: true
112
+ });
113
+ candidate.subagents.push({
114
+ id: subagent.id,
115
+ nodeSource: resolvedSubagent.source
116
+ });
117
+ edges.push({
118
+ from: canonicalPath,
119
+ kind: "subagent",
120
+ label: subagent.id,
121
+ to: resolvedSubagent.source
122
+ });
123
+ }
124
+ visitStack.pop();
125
+ return candidate;
126
+ };
127
+ const visitTeam = async (manifestPath) => {
128
+ const canonicalPath = getCanonicalManifestPath(manifestPath);
129
+ if (visitStack.includes(canonicalPath)) {
130
+ throw new SpawnfileError("compile_error", `Cycle detected while visiting ${canonicalPath}`);
131
+ }
132
+ const loadedManifest = await getLoadedManifest(canonicalPath);
133
+ if (!isTeamManifest(loadedManifest.manifest)) {
134
+ throw new SpawnfileError("compile_error", `Expected team manifest, got ${loadedManifest.manifest.kind} at ${canonicalPath}`);
135
+ }
136
+ const sharedSkills = await loadResolvedSkills(canonicalPath, loadedManifest.manifest.shared?.skills);
137
+ validateEffectiveSkillRequirements(loadedManifest.manifest.name, getMcpNames(loadedManifest.manifest.shared?.mcp_servers ?? []), sharedSkills);
138
+ const structure = loadedManifest.manifest.structure;
139
+ const memberIds = loadedManifest.manifest.members.map((member) => member.id);
140
+ const resolvedExternal = structure.external
141
+ ?? (structure.mode === "hierarchical" && structure.leader
142
+ ? [structure.leader]
143
+ : memberIds);
144
+ const candidate = {
145
+ docs: await loadResolvedDocuments(canonicalPath, loadedManifest.manifest.docs),
146
+ kind: "team",
147
+ members: [],
148
+ name: loadedManifest.manifest.name,
149
+ policyMode: loadedManifest.manifest.policy?.mode ?? null,
150
+ policyOnDegrade: loadedManifest.manifest.policy?.on_degrade ?? null,
151
+ shared: {
152
+ env: loadedManifest.manifest.shared?.env ?? {},
153
+ mcpServers: loadedManifest.manifest.shared?.mcp_servers ?? [],
154
+ secrets: loadedManifest.manifest.shared?.secrets ?? [],
155
+ skills: sharedSkills
156
+ },
157
+ source: canonicalPath,
158
+ structure: {
159
+ external: resolvedExternal,
160
+ leader: structure.leader ?? null,
161
+ mode: structure.mode
162
+ }
163
+ };
164
+ const fingerprint = getTeamFingerprint(candidate);
165
+ const existingFingerprint = fingerprintCache.get(canonicalPath);
166
+ if (existingFingerprint && existingFingerprint !== fingerprint) {
167
+ throw new SpawnfileError("compile_error", `Team manifest ${canonicalPath} resolves differently across compile contexts`);
168
+ }
169
+ const cachedNode = nodeCache.get(canonicalPath);
170
+ if (cachedNode) {
171
+ return cachedNode.value;
172
+ }
173
+ fingerprintCache.set(canonicalPath, fingerprint);
174
+ nodeCache.set(canonicalPath, {
175
+ runtimeName: null,
176
+ source: canonicalPath,
177
+ value: candidate
178
+ });
179
+ visitStack.push(canonicalPath);
180
+ for (const member of loadedManifest.manifest.members) {
181
+ const childManifestPath = getManifestPath(resolveProjectPath(canonicalPath, member.ref));
182
+ const childManifest = await getLoadedManifest(childManifestPath);
183
+ let resolvedMember;
184
+ if (isAgentManifest(childManifest.manifest)) {
185
+ const resolvedAgent = await visitAgent(childManifestPath, {
186
+ inheritedShared: {
187
+ manifestPath: canonicalPath,
188
+ surface: loadedManifest.manifest.shared
189
+ },
190
+ isSubagent: false
191
+ });
192
+ resolvedMember = {
193
+ id: member.id,
194
+ kind: "agent",
195
+ nodeSource: resolvedAgent.source,
196
+ runtimeName: resolvedAgent.runtime.name
197
+ };
198
+ }
199
+ else {
200
+ const resolvedTeam = await visitTeam(childManifestPath);
201
+ resolvedMember = {
202
+ id: member.id,
203
+ kind: "team",
204
+ nodeSource: resolvedTeam.source,
205
+ runtimeName: null
206
+ };
207
+ }
208
+ candidate.members.push(resolvedMember);
209
+ edges.push({
210
+ from: canonicalPath,
211
+ kind: "team_member",
212
+ label: member.id,
213
+ to: resolvedMember.nodeSource
214
+ });
215
+ }
216
+ visitStack.pop();
217
+ return candidate;
218
+ };
219
+ const rootLoadedManifest = await getLoadedManifest(rootManifestPath);
220
+ if (isAgentManifest(rootLoadedManifest.manifest)) {
221
+ await visitAgent(rootManifestPath, { isSubagent: false });
222
+ }
223
+ else {
224
+ await visitTeam(rootManifestPath);
225
+ }
226
+ const nodes = assignStableNodeIds([...nodeCache.values()]
227
+ .sort((left, right) => left.source.localeCompare(right.source))
228
+ .map((node) => ({
229
+ id: "",
230
+ kind: node.value.kind,
231
+ runtimeName: node.runtimeName,
232
+ slug: "",
233
+ source: node.source,
234
+ value: node.value
235
+ })));
236
+ const idBySource = new Map(nodes.map((node) => [node.value.source, node.id]));
237
+ const compilePlanNodes = nodes;
238
+ const compilePlanEdges = edges.map((edge) => ({
239
+ ...edge,
240
+ from: idBySource.get(edge.from) ?? edge.from,
241
+ to: idBySource.get(edge.to) ?? edge.to
242
+ }));
243
+ const runtimes = compilePlanNodes.reduce((groups, node) => {
244
+ if (!node.runtimeName) {
245
+ return groups;
246
+ }
247
+ const group = groups[node.runtimeName] ?? { nodeIds: [] };
248
+ group.nodeIds.push(node.id);
249
+ groups[node.runtimeName] = group;
250
+ return groups;
251
+ }, {});
252
+ return {
253
+ edges: compilePlanEdges,
254
+ nodes: compilePlanNodes,
255
+ root: rootManifestPath,
256
+ runtimes
257
+ };
258
+ };
@@ -0,0 +1,20 @@
1
+ import { type CompileProjectOptions, type CompileProjectResult } from "./compileProject.js";
2
+ export interface DockerBuildInvocation {
3
+ args: string[];
4
+ command: string;
5
+ cwd: string;
6
+ imageTag: string;
7
+ }
8
+ export type DockerBuildRunner = (invocation: DockerBuildInvocation) => Promise<void>;
9
+ export interface BuildProjectOptions extends CompileProjectOptions {
10
+ buildRunner?: DockerBuildRunner;
11
+ dockerCommand?: string;
12
+ imageTag?: string;
13
+ }
14
+ export interface BuildProjectResult extends CompileProjectResult {
15
+ imageTag: string;
16
+ }
17
+ export declare const createDefaultImageTag: (projectRoot: string) => string;
18
+ export declare const createDockerBuildInvocation: (outputDirectory: string, imageTag: string, dockerCommand?: string) => DockerBuildInvocation;
19
+ export declare const runDockerBuild: DockerBuildRunner;
20
+ export declare const buildProject: (inputPath: string, options?: BuildProjectOptions) => Promise<BuildProjectResult>;
@@ -0,0 +1,52 @@
1
+ import path from "node:path";
2
+ import { spawn } from "node:child_process";
3
+ import { SpawnfileError } from "../shared/index.js";
4
+ import { compileProject } from "./compileProject.js";
5
+ import { slugify } from "./helpers.js";
6
+ export const createDefaultImageTag = (projectRoot) => {
7
+ const baseName = slugify(path.basename(projectRoot));
8
+ return `spawnfile-${baseName || "project"}`;
9
+ };
10
+ const resolveImageTagRoot = (inputPath) => {
11
+ const resolvedPath = path.resolve(inputPath);
12
+ return path.basename(resolvedPath).toLowerCase() === "spawnfile"
13
+ ? path.dirname(resolvedPath)
14
+ : resolvedPath;
15
+ };
16
+ export const createDockerBuildInvocation = (outputDirectory, imageTag, dockerCommand = "docker") => ({
17
+ args: ["build", "-t", imageTag, "."],
18
+ command: dockerCommand,
19
+ cwd: outputDirectory,
20
+ imageTag
21
+ });
22
+ export const runDockerBuild = async (invocation) => new Promise((resolve, reject) => {
23
+ const child = spawn(invocation.command, invocation.args, {
24
+ cwd: invocation.cwd,
25
+ stdio: "inherit"
26
+ });
27
+ child.once("error", (error) => {
28
+ reject(new SpawnfileError("compile_error", `Unable to start docker build for ${invocation.imageTag}: ${error.message}`));
29
+ });
30
+ child.once("exit", (code, signal) => {
31
+ if (code === 0) {
32
+ resolve();
33
+ return;
34
+ }
35
+ reject(new SpawnfileError("compile_error", signal
36
+ ? `Docker build for ${invocation.imageTag} exited from signal ${signal}`
37
+ : `Docker build for ${invocation.imageTag} failed with exit code ${code ?? "unknown"}`));
38
+ });
39
+ });
40
+ export const buildProject = async (inputPath, options = {}) => {
41
+ const compileResult = await compileProject(inputPath, {
42
+ clean: options.clean,
43
+ outputDirectory: options.outputDirectory
44
+ });
45
+ const imageTag = options.imageTag ?? createDefaultImageTag(resolveImageTagRoot(inputPath));
46
+ const invocation = createDockerBuildInvocation(compileResult.outputDirectory, imageTag, options.dockerCommand);
47
+ await (options.buildRunner ?? runDockerBuild)(invocation);
48
+ return {
49
+ ...compileResult,
50
+ imageTag
51
+ };
52
+ };
@@ -0,0 +1,7 @@
1
+ import type { ResolvedAgentNode, ResolvedSkill, ResolvedTeamNode } from "./types.js";
2
+ export declare const getMcpNames: (servers: Array<{
3
+ name: string;
4
+ }>) => Set<string>;
5
+ export declare const validateEffectiveSkillRequirements: (nodeName: string, mcpNames: Set<string>, skills: ResolvedSkill[]) => void;
6
+ export declare const getAgentFingerprint: (node: ResolvedAgentNode) => string;
7
+ export declare const getTeamFingerprint: (node: ResolvedTeamNode) => string;
@@ -0,0 +1,39 @@
1
+ import { SpawnfileError } from "../shared/index.js";
2
+ import { stableStringify } from "./helpers.js";
3
+ export const getMcpNames = (servers) => new Set(servers.map((server) => server.name));
4
+ export const validateEffectiveSkillRequirements = (nodeName, mcpNames, skills) => {
5
+ for (const skill of skills) {
6
+ for (const mcpName of skill.requiresMcp) {
7
+ if (!mcpNames.has(mcpName)) {
8
+ throw new SpawnfileError("validation_error", `Skill ${skill.name} on ${nodeName} requires undeclared MCP server: ${mcpName}`);
9
+ }
10
+ }
11
+ }
12
+ };
13
+ export const getAgentFingerprint = (node) => stableStringify({
14
+ env: node.env,
15
+ execution: node.execution,
16
+ mcpServers: node.mcpServers,
17
+ runtime: node.runtime,
18
+ secrets: node.secrets,
19
+ skills: node.skills.map((skill) => ({
20
+ name: skill.name,
21
+ ref: skill.ref,
22
+ requiresMcp: skill.requiresMcp
23
+ })),
24
+ surfaces: node.surfaces
25
+ });
26
+ export const getTeamFingerprint = (node) => stableStringify({
27
+ members: node.members,
28
+ structure: node.structure,
29
+ shared: {
30
+ env: node.shared.env,
31
+ mcpServers: node.shared.mcpServers,
32
+ secrets: node.shared.secrets,
33
+ skills: node.shared.skills.map((skill) => ({
34
+ name: skill.name,
35
+ ref: skill.ref,
36
+ requiresMcp: skill.requiresMcp
37
+ }))
38
+ }
39
+ });
@@ -0,0 +1,11 @@
1
+ import { CompileReport } from "../report/index.js";
2
+ export interface CompileProjectOptions {
3
+ clean?: boolean;
4
+ outputDirectory?: string;
5
+ }
6
+ export interface CompileProjectResult {
7
+ outputDirectory: string;
8
+ report: CompileReport;
9
+ reportPath: string;
10
+ }
11
+ export declare const compileProject: (inputPath: string, options?: CompileProjectOptions) => Promise<CompileProjectResult>;