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,13 @@
1
+ import { getRuntimeAdapter } from "../runtime/index.js";
2
+ export const assertRuntimeSupportsAgentSurfaces = (runtimeName, surfaces, nodeName) => {
3
+ const adapter = getRuntimeAdapter(runtimeName);
4
+ try {
5
+ adapter.assertSupportedSurfaces?.(surfaces);
6
+ }
7
+ catch (error) {
8
+ if (error instanceof Error) {
9
+ error.message = `${error.message} on ${nodeName}`;
10
+ }
11
+ throw error;
12
+ }
13
+ };
@@ -0,0 +1,21 @@
1
+ import { DocsBlock, McpServer, Secret, SharedSurface, SkillReference } from "../manifest/index.js";
2
+ import { StringMap } from "../shared/index.js";
3
+ import { ResolvedDocument, ResolvedSkill } from "./types.js";
4
+ export declare const mergeSkills: (sharedSkills?: SkillReference[], localSkills?: SkillReference[]) => SkillReference[];
5
+ export declare const mergeResolvedSkills: (sharedSkills?: ResolvedSkill[], localSkills?: ResolvedSkill[]) => ResolvedSkill[];
6
+ export declare const mergeMcpServers: (sharedServers?: McpServer[], localServers?: McpServer[]) => McpServer[];
7
+ export declare const mergeSecrets: (sharedSecrets?: Secret[], localSecrets?: Secret[]) => Secret[];
8
+ export declare const mergeEnv: (sharedEnv?: StringMap, localEnv?: StringMap) => StringMap;
9
+ export declare const loadResolvedDocuments: (manifestPath: string, docs: DocsBlock | undefined) => Promise<ResolvedDocument[]>;
10
+ export declare const loadResolvedSkills: (manifestPath: string, skills?: SkillReference[]) => Promise<ResolvedSkill[]>;
11
+ export declare const mergeSharedSurface: (shared: SharedSurface | undefined, local: {
12
+ env: StringMap | undefined;
13
+ mcpServers: McpServer[] | undefined;
14
+ secrets: Secret[] | undefined;
15
+ skills: SkillReference[] | undefined;
16
+ }) => {
17
+ env: StringMap;
18
+ mcpServers: McpServer[];
19
+ secrets: Secret[];
20
+ skills: SkillReference[];
21
+ };
@@ -0,0 +1,59 @@
1
+ import { readUtf8File, resolveProjectPath } from "../filesystem/index.js";
2
+ import { parseSkillFrontmatter } from "../manifest/index.js";
3
+ const mergeByKey = (sharedValues, localValues, getKey) => {
4
+ const values = new Map();
5
+ for (const value of sharedValues) {
6
+ values.set(getKey(value), value);
7
+ }
8
+ for (const value of localValues) {
9
+ values.set(getKey(value), value);
10
+ }
11
+ return [...values.values()];
12
+ };
13
+ export const mergeSkills = (sharedSkills = [], localSkills = []) => mergeByKey(sharedSkills, localSkills, (skill) => skill.ref);
14
+ export const mergeResolvedSkills = (sharedSkills = [], localSkills = []) => mergeByKey(sharedSkills, localSkills, (skill) => skill.ref);
15
+ export const mergeMcpServers = (sharedServers = [], localServers = []) => mergeByKey(sharedServers, localServers, (server) => server.name);
16
+ export const mergeSecrets = (sharedSecrets = [], localSecrets = []) => mergeByKey(sharedSecrets, localSecrets, (secret) => secret.name);
17
+ export const mergeEnv = (sharedEnv = {}, localEnv = {}) => ({
18
+ ...sharedEnv,
19
+ ...localEnv
20
+ });
21
+ export const loadResolvedDocuments = async (manifestPath, docs) => {
22
+ if (!docs) {
23
+ return [];
24
+ }
25
+ const documents = [
26
+ ["heartbeat", docs.heartbeat],
27
+ ["identity", docs.identity],
28
+ ["memory", docs.memory],
29
+ ["soul", docs.soul],
30
+ ["system", docs.system],
31
+ ...Object.entries(docs.extras ?? {}).map(([name, relativePath]) => [`extras.${name}`, relativePath])
32
+ ].filter((entry) => Boolean(entry[1]));
33
+ return Promise.all(documents.map(async ([role, relativePath]) => {
34
+ const sourcePath = resolveProjectPath(manifestPath, relativePath);
35
+ return {
36
+ content: await readUtf8File(sourcePath),
37
+ role,
38
+ sourcePath
39
+ };
40
+ }));
41
+ };
42
+ export const loadResolvedSkills = async (manifestPath, skills = []) => Promise.all(skills.map(async (skill) => {
43
+ const sourcePath = resolveProjectPath(manifestPath, `${skill.ref}/SKILL.md`);
44
+ const content = await readUtf8File(sourcePath);
45
+ const frontmatter = parseSkillFrontmatter(content);
46
+ return {
47
+ content,
48
+ name: frontmatter.name,
49
+ ref: skill.ref,
50
+ requiresMcp: skill.requires?.mcp ?? [],
51
+ sourcePath
52
+ };
53
+ }));
54
+ export const mergeSharedSurface = (shared, local) => ({
55
+ env: mergeEnv(shared?.env, local.env),
56
+ mcpServers: mergeMcpServers(shared?.mcp_servers, local.mcpServers),
57
+ secrets: mergeSecrets(shared?.secrets, local.secrets),
58
+ skills: mergeSkills(shared?.skills, local.skills)
59
+ });
@@ -0,0 +1,7 @@
1
+ export interface SyncProjectAuthOptions {
2
+ claudeCodeDirectory?: string;
3
+ codexDirectory?: string;
4
+ envFilePath?: string;
5
+ profileName: string;
6
+ }
7
+ export declare const syncProjectAuth: (inputPath: string, options: SyncProjectAuthOptions) => Promise<import("../auth/types.js").ResolvedAuthProfile>;
@@ -0,0 +1,65 @@
1
+ import { ensureAuthProfile, importClaudeCodeAuth, importCodexAuth, parseEnvFile, requireAuthProfile, setAuthProfileEnv } from "../auth/index.js";
2
+ import { readUtf8File } from "../filesystem/index.js";
3
+ import { SpawnfileError } from "../shared/index.js";
4
+ import { listAgentSurfaceSecretNames } from "./agentSurfaces.js";
5
+ import { buildCompilePlan } from "./buildCompilePlan.js";
6
+ import { listExecutionModelSecretNames, resolveExecutionModelAuthMethods } from "./modelEnv.js";
7
+ const resolveAuthRequirements = async (inputPath) => {
8
+ const plan = await buildCompilePlan(inputPath);
9
+ const methods = new Set();
10
+ const envNames = new Set();
11
+ for (const node of plan.nodes) {
12
+ if (node.value.kind !== "agent") {
13
+ continue;
14
+ }
15
+ for (const method of Object.values(resolveExecutionModelAuthMethods(node.value.execution))) {
16
+ methods.add(method);
17
+ }
18
+ for (const envName of listExecutionModelSecretNames(node.value.execution)) {
19
+ envNames.add(envName);
20
+ }
21
+ for (const envName of listAgentSurfaceSecretNames(node.value.surfaces)) {
22
+ envNames.add(envName);
23
+ }
24
+ }
25
+ return { envNames, methods };
26
+ };
27
+ const resolveRequiredEnv = async (envNames, envFilePath) => {
28
+ if (envNames.size === 0) {
29
+ return {};
30
+ }
31
+ const fileEnv = envFilePath ? parseEnvFile(await readUtf8File(envFilePath)) : {};
32
+ const resolvedEnv = {};
33
+ const missingEnv = [];
34
+ for (const envName of [...envNames].sort()) {
35
+ const processValue = process.env[envName];
36
+ if (typeof processValue === "string" && processValue.length > 0) {
37
+ resolvedEnv[envName] = processValue;
38
+ continue;
39
+ }
40
+ const fileValue = fileEnv[envName];
41
+ if (typeof fileValue === "string" && fileValue.length > 0) {
42
+ resolvedEnv[envName] = fileValue;
43
+ continue;
44
+ }
45
+ missingEnv.push(envName);
46
+ }
47
+ if (missingEnv.length > 0) {
48
+ throw new SpawnfileError("validation_error", `Missing required auth env: ${missingEnv.join(", ")}`);
49
+ }
50
+ return resolvedEnv;
51
+ };
52
+ export const syncProjectAuth = async (inputPath, options) => {
53
+ const { envNames, methods } = await resolveAuthRequirements(inputPath);
54
+ await ensureAuthProfile(options.profileName);
55
+ if (methods.has("codex")) {
56
+ await importCodexAuth(options.profileName, options.codexDirectory);
57
+ }
58
+ if (methods.has("claude-code")) {
59
+ await importClaudeCodeAuth(options.profileName, options.claudeCodeDirectory);
60
+ }
61
+ if (envNames.size > 0) {
62
+ await setAuthProfileEnv(options.profileName, await resolveRequiredEnv(envNames, options.envFilePath));
63
+ }
64
+ return requireAuthProfile(options.profileName);
65
+ };
@@ -0,0 +1,134 @@
1
+ import { ExecutionBlock, McpServer, ModelEndpoint, Secret } from "../manifest/index.js";
2
+ import { ModelAuthMethod, StringMap } from "../shared/index.js";
3
+ export interface ResolvedDocument {
4
+ content: string;
5
+ role: string;
6
+ sourcePath: string;
7
+ }
8
+ export interface ResolvedSkill {
9
+ content: string;
10
+ name: string;
11
+ ref: string;
12
+ requiresMcp: string[];
13
+ sourcePath: string;
14
+ }
15
+ export interface ResolvedRuntime {
16
+ name: string;
17
+ options: Record<string, unknown>;
18
+ }
19
+ export interface ResolvedDiscordSurface {
20
+ access?: {
21
+ channels: string[];
22
+ guilds: string[];
23
+ mode: "allowlist" | "open" | "pairing";
24
+ users: string[];
25
+ };
26
+ botTokenSecret: string;
27
+ }
28
+ export interface ResolvedTelegramSurface {
29
+ access?: {
30
+ chats: string[];
31
+ mode: "allowlist" | "open" | "pairing";
32
+ users: string[];
33
+ };
34
+ botTokenSecret: string;
35
+ }
36
+ export interface ResolvedWhatsAppSurface {
37
+ access?: {
38
+ groups: string[];
39
+ mode: "allowlist" | "open" | "pairing";
40
+ users: string[];
41
+ };
42
+ }
43
+ export interface ResolvedSlackSurface {
44
+ access?: {
45
+ channels: string[];
46
+ mode: "allowlist" | "open" | "pairing";
47
+ users: string[];
48
+ };
49
+ appTokenSecret: string;
50
+ botTokenSecret: string;
51
+ }
52
+ export interface ResolvedAgentSurfaces {
53
+ discord?: ResolvedDiscordSurface;
54
+ slack?: ResolvedSlackSurface;
55
+ telegram?: ResolvedTelegramSurface;
56
+ whatsapp?: ResolvedWhatsAppSurface;
57
+ }
58
+ export interface EffectiveModelTarget {
59
+ auth: {
60
+ key?: string;
61
+ method: ModelAuthMethod;
62
+ };
63
+ endpoint?: ModelEndpoint;
64
+ name: string;
65
+ provider: string;
66
+ }
67
+ export interface ResolvedSubagentRef {
68
+ id: string;
69
+ nodeSource: string;
70
+ }
71
+ export interface ResolvedMemberRef {
72
+ id: string;
73
+ kind: "agent" | "team";
74
+ nodeSource: string;
75
+ runtimeName: string | null;
76
+ }
77
+ export interface ResolvedAgentNode {
78
+ docs: ResolvedDocument[];
79
+ env: StringMap;
80
+ execution: ExecutionBlock | undefined;
81
+ kind: "agent";
82
+ mcpServers: McpServer[];
83
+ name: string;
84
+ policyMode: string | null;
85
+ policyOnDegrade: string | null;
86
+ runtime: ResolvedRuntime;
87
+ secrets: Secret[];
88
+ skills: ResolvedSkill[];
89
+ source: string;
90
+ surfaces?: ResolvedAgentSurfaces;
91
+ subagents: ResolvedSubagentRef[];
92
+ }
93
+ export interface ResolvedTeamStructure {
94
+ external: string[];
95
+ leader: string | null;
96
+ mode: "hierarchical" | "swarm";
97
+ }
98
+ export interface ResolvedTeamNode {
99
+ docs: ResolvedDocument[];
100
+ kind: "team";
101
+ members: ResolvedMemberRef[];
102
+ name: string;
103
+ policyMode: string | null;
104
+ policyOnDegrade: string | null;
105
+ shared: {
106
+ env: StringMap;
107
+ mcpServers: McpServer[];
108
+ secrets: Secret[];
109
+ skills: ResolvedSkill[];
110
+ };
111
+ source: string;
112
+ structure: ResolvedTeamStructure;
113
+ }
114
+ export interface CompilePlanEdge {
115
+ from: string;
116
+ kind: "subagent" | "team_member";
117
+ label: string;
118
+ to: string;
119
+ }
120
+ export interface CompilePlanNode {
121
+ id: string;
122
+ kind: "agent" | "team";
123
+ runtimeName: string | null;
124
+ slug: string;
125
+ value: ResolvedAgentNode | ResolvedTeamNode;
126
+ }
127
+ export interface CompilePlan {
128
+ edges: CompilePlanEdge[];
129
+ nodes: CompilePlanNode[];
130
+ root: string;
131
+ runtimes: Record<string, {
132
+ nodeIds: string[];
133
+ }>;
134
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import { type ModelAuthMethod, type ModelEndpointCompatibility } from "../shared/index.js";
2
+ export interface ProjectModelTargetOptions {
3
+ authKey?: string;
4
+ authMethod?: ModelAuthMethod;
5
+ endpointBaseUrl?: string;
6
+ endpointCompatibility?: ModelEndpointCompatibility;
7
+ name: string;
8
+ path?: string;
9
+ provider: string;
10
+ recursive?: boolean;
11
+ }
12
+ export interface UpdateProjectModelsResult {
13
+ updatedFiles: string[];
14
+ }
15
+ export declare const setProjectPrimaryModel: (options: ProjectModelTargetOptions) => Promise<UpdateProjectModelsResult>;
16
+ export declare const addProjectModelFallback: (options: ProjectModelTargetOptions) => Promise<UpdateProjectModelsResult>;
17
+ export declare const clearProjectModelFallbacks: (options?: {
18
+ path?: string;
19
+ recursive?: boolean;
20
+ }) => Promise<UpdateProjectModelsResult>;
@@ -0,0 +1,181 @@
1
+ import path from "node:path";
2
+ import { getCanonicalManifestPath, getManifestPath, getProjectRoot, writeUtf8File } from "../filesystem/index.js";
3
+ import { loadManifest, renderSpawnfile } from "../manifest/index.js";
4
+ import { SpawnfileError } from "../shared/index.js";
5
+ import { resolveEffectiveModelTarget } from "./modelEnv.js";
6
+ const TEAM_MODEL_COMMAND_ERROR = "spawnfile model commands only write agent manifests; use --recursive to update descendant agents of a team project";
7
+ const AUTH_METHOD_PROVIDER_HINTS = {
8
+ "api_key": "use a model provider like openai or anthropic, then pass --auth api_key",
9
+ "claude-code": "use provider anthropic, then pass --auth claude-code",
10
+ codex: "use provider openai, then pass --auth codex",
11
+ none: "use a model provider like local, then pass --auth none"
12
+ };
13
+ const resolveTargetManifestPath = (inputPath) => getManifestPath(path.resolve(inputPath ?? process.cwd()));
14
+ const toModelTarget = (target) => ({
15
+ ...(target.endpoint ? { endpoint: target.endpoint } : {}),
16
+ auth: target.auth,
17
+ name: target.name,
18
+ provider: target.provider
19
+ });
20
+ const normalizeExecutionModel = (execution) => {
21
+ if (!execution?.model?.primary) {
22
+ return undefined;
23
+ }
24
+ const executionWithModel = { model: execution.model };
25
+ return {
26
+ ...(execution.model.fallback
27
+ ? {
28
+ fallback: execution.model.fallback.map((target) => toModelTarget(resolveEffectiveModelTarget(target, executionWithModel)))
29
+ }
30
+ : {}),
31
+ primary: toModelTarget(resolveEffectiveModelTarget(execution.model.primary, executionWithModel))
32
+ };
33
+ };
34
+ const buildModelTarget = (options) => {
35
+ const usesEndpoint = options.provider === "custom" || options.provider === "local";
36
+ const hasEndpointInput = Boolean(options.endpointBaseUrl || options.endpointCompatibility);
37
+ const authMethodHint = AUTH_METHOD_PROVIDER_HINTS[options.provider];
38
+ if (authMethodHint) {
39
+ throw new SpawnfileError("validation_error", `Model provider must not be an auth method: ${options.provider}; ${authMethodHint}`);
40
+ }
41
+ if (options.authKey && options.authMethod !== "api_key") {
42
+ throw new SpawnfileError("validation_error", "Model auth key is only valid with api_key auth");
43
+ }
44
+ if (!usesEndpoint && hasEndpointInput) {
45
+ throw new SpawnfileError("validation_error", "Only custom and local models accept --base-url and --compat");
46
+ }
47
+ if (usesEndpoint && (!options.endpointBaseUrl || !options.endpointCompatibility)) {
48
+ throw new SpawnfileError("validation_error", `${options.provider} models require both --base-url and --compat`);
49
+ }
50
+ if (options.provider === "custom" && !options.authMethod) {
51
+ throw new SpawnfileError("validation_error", "Custom models require --auth");
52
+ }
53
+ if (usesEndpoint && options.authMethod === "api_key" && !options.authKey) {
54
+ throw new SpawnfileError("validation_error", `${options.provider} api_key auth requires --key`);
55
+ }
56
+ return {
57
+ ...(options.authMethod
58
+ ? {
59
+ auth: {
60
+ ...(options.authKey ? { key: options.authKey } : {}),
61
+ method: options.authMethod
62
+ }
63
+ }
64
+ : {}),
65
+ ...(usesEndpoint
66
+ ? {
67
+ endpoint: {
68
+ base_url: options.endpointBaseUrl,
69
+ compatibility: options.endpointCompatibility
70
+ }
71
+ }
72
+ : {}),
73
+ name: options.name,
74
+ provider: options.provider
75
+ };
76
+ };
77
+ const collectManifestPaths = async (manifestPath, recursive, visited = new Set()) => {
78
+ const canonicalPath = getCanonicalManifestPath(manifestPath);
79
+ if (visited.has(canonicalPath)) {
80
+ return [];
81
+ }
82
+ visited.add(canonicalPath);
83
+ if (!recursive) {
84
+ return [canonicalPath];
85
+ }
86
+ const loadedManifest = await loadManifest(canonicalPath);
87
+ const childRefs = loadedManifest.manifest.kind === "team"
88
+ ? loadedManifest.manifest.members.map((member) => member.ref)
89
+ : (loadedManifest.manifest.subagents ?? []).map((subagent) => subagent.ref);
90
+ const nestedPaths = await Promise.all(childRefs.map((ref) => collectManifestPaths(getManifestPath(path.resolve(getProjectRoot(canonicalPath), ref)), true, visited)));
91
+ return [canonicalPath, ...nestedPaths.flat()];
92
+ };
93
+ const updateManifestExecution = (manifest, model) => ({
94
+ ...manifest,
95
+ execution: {
96
+ ...manifest.execution,
97
+ model
98
+ }
99
+ });
100
+ const manifestChanged = (current, next) => JSON.stringify(current) !== JSON.stringify(next);
101
+ const assertModelMutationAllowed = (manifest, recursive) => {
102
+ if (manifest.kind === "agent") {
103
+ return true;
104
+ }
105
+ if (recursive) {
106
+ return false;
107
+ }
108
+ throw new SpawnfileError("validation_error", TEAM_MODEL_COMMAND_ERROR);
109
+ };
110
+ const rewriteTouchedManifests = async (manifestPaths, mutate) => {
111
+ const updatedFiles = [];
112
+ for (const manifestPath of manifestPaths) {
113
+ const loadedManifest = await loadManifest(manifestPath);
114
+ const nextManifest = mutate(loadedManifest.manifest, manifestPath);
115
+ if (!nextManifest || !manifestChanged(loadedManifest.manifest, nextManifest)) {
116
+ continue;
117
+ }
118
+ await writeUtf8File(manifestPath, renderSpawnfile(nextManifest));
119
+ updatedFiles.push(manifestPath);
120
+ }
121
+ return { updatedFiles };
122
+ };
123
+ export const setProjectPrimaryModel = async (options) => {
124
+ const modelTarget = buildModelTarget(options);
125
+ const recursive = options.recursive ?? false;
126
+ const manifestPaths = await collectManifestPaths(resolveTargetManifestPath(options.path), recursive);
127
+ return rewriteTouchedManifests(manifestPaths, (manifest) => {
128
+ if (!assertModelMutationAllowed(manifest, recursive)) {
129
+ return null;
130
+ }
131
+ const normalizedModel = normalizeExecutionModel(manifest.execution);
132
+ return updateManifestExecution(manifest, {
133
+ ...(normalizedModel?.fallback ? { fallback: normalizedModel.fallback } : {}),
134
+ primary: modelTarget
135
+ });
136
+ });
137
+ };
138
+ export const addProjectModelFallback = async (options) => {
139
+ const modelTarget = buildModelTarget(options);
140
+ const recursive = options.recursive ?? false;
141
+ const manifestPaths = await collectManifestPaths(resolveTargetManifestPath(options.path), recursive);
142
+ return rewriteTouchedManifests(manifestPaths, (manifest, manifestPath) => {
143
+ if (!assertModelMutationAllowed(manifest, recursive)) {
144
+ return null;
145
+ }
146
+ const normalizedModel = normalizeExecutionModel(manifest.execution);
147
+ if (!normalizedModel) {
148
+ if (recursive) {
149
+ return null;
150
+ }
151
+ throw new SpawnfileError("validation_error", `Manifest at ${manifestPath} must declare a primary model before adding fallback models`);
152
+ }
153
+ const existingFallback = normalizedModel.fallback ?? [];
154
+ const nextFallback = existingFallback.some((entry) => JSON.stringify(entry) === JSON.stringify(modelTarget))
155
+ ? existingFallback
156
+ : [...existingFallback, modelTarget];
157
+ return updateManifestExecution(manifest, {
158
+ fallback: nextFallback,
159
+ primary: normalizedModel.primary
160
+ });
161
+ });
162
+ };
163
+ export const clearProjectModelFallbacks = async (options = {}) => {
164
+ const recursive = options.recursive ?? false;
165
+ const manifestPaths = await collectManifestPaths(resolveTargetManifestPath(options.path), recursive);
166
+ return rewriteTouchedManifests(manifestPaths, (manifest, manifestPath) => {
167
+ if (!assertModelMutationAllowed(manifest, recursive)) {
168
+ return null;
169
+ }
170
+ const normalizedModel = normalizeExecutionModel(manifest.execution);
171
+ if (!normalizedModel) {
172
+ if (recursive) {
173
+ return null;
174
+ }
175
+ throw new SpawnfileError("validation_error", `Manifest at ${manifestPath} does not declare any execution model`);
176
+ }
177
+ return updateManifestExecution(manifest, {
178
+ primary: normalizedModel.primary
179
+ });
180
+ });
181
+ };
@@ -0,0 +1,16 @@
1
+ {
2
+ "agents": {
3
+ "defaults": {
4
+ "workspace": "/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/workspace",
5
+ "restrict_to_workspace": true,
6
+ "model_name": "claude-sonnet-4.6"
7
+ }
8
+ },
9
+ "model_list": [
10
+ {
11
+ "api_key": "file://secrets/ANTHROPIC_API_KEY",
12
+ "model_name": "claude-sonnet-4.6",
13
+ "model": "anthropic/claude-sonnet-4.6"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1 @@
1
+ You are a concise assistant. Reply with exactly the requested token and nothing else.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,40 @@
1
+ import { Command } from "commander";
2
+ import { isSpawnfileError } from "../shared/index.js";
3
+ import { runDockerAuthE2E } from "./dockerAuth.js";
4
+ const collect = (value, previous) => [...previous, value];
5
+ const main = async () => {
6
+ const program = new Command();
7
+ program
8
+ .name("spawnfile-e2e")
9
+ .description("Run opt-in Docker auth E2E scenarios against real runtime images")
10
+ .option("--scenario <id>", "Scenario id to run", collect, [])
11
+ .option("--runtime <runtime>", "Runtime filter", collect, [])
12
+ .option("--auth <method>", "Auth method filter", collect, [])
13
+ .option("--env-file <path>", "Env file for api_key scenarios")
14
+ .option("--claude-from <directory>", "Claude Code config directory override")
15
+ .option("--codex-from <directory>", "Codex config directory override")
16
+ .option("--keep-artifacts", "Keep temporary projects and compile output")
17
+ .option("--keep-images", "Keep built Docker images after each scenario");
18
+ await program.parseAsync(process.argv);
19
+ const options = program.opts();
20
+ const result = await runDockerAuthE2E({
21
+ authMethods: options.auth,
22
+ claudeCodeDirectory: options.claudeFrom,
23
+ codexDirectory: options.codexFrom,
24
+ envFilePath: options.envFile,
25
+ keepArtifacts: options.keepArtifacts,
26
+ keepImages: options.keepImages,
27
+ runtimes: options.runtime,
28
+ scenarioIds: options.scenario
29
+ });
30
+ console.log(`Docker auth E2E passed (${result.results.length} scenarios)`);
31
+ };
32
+ main().catch((error) => {
33
+ const message = isSpawnfileError(error)
34
+ ? `${error.code}: ${error.message}`
35
+ : error instanceof Error
36
+ ? error.message
37
+ : String(error);
38
+ process.stderr.write(`${message}\n`);
39
+ process.exitCode = 1;
40
+ });
@@ -0,0 +1,18 @@
1
+ import type { DockerAuthE2EFilters, DockerAuthE2EScenarioResult } from "./types.js";
2
+ export interface DockerAuthE2ELogger {
3
+ error(message: string): void;
4
+ info(message: string): void;
5
+ }
6
+ export interface RunDockerAuthE2EOptions extends DockerAuthE2EFilters {
7
+ claudeCodeDirectory?: string;
8
+ codexDirectory?: string;
9
+ dockerCommand?: string;
10
+ envFilePath?: string;
11
+ keepArtifacts?: boolean;
12
+ keepImages?: boolean;
13
+ logger?: DockerAuthE2ELogger;
14
+ }
15
+ export interface RunDockerAuthE2EResult {
16
+ results: DockerAuthE2EScenarioResult[];
17
+ }
18
+ export declare const runDockerAuthE2E: (options?: RunDockerAuthE2EOptions) => Promise<RunDockerAuthE2EResult>;