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,204 @@
1
+ import { listEffectiveExecutionModelTargets, listExecutionModelSecretNames, resolveModelProviderEnvName } from "../../compiler/modelEnv.js";
2
+ import { SpawnfileError } from "../../shared/index.js";
3
+ import { createAgentCapabilities, createDiagnostic, createDocumentFiles, createSkillFiles } from "../common.js";
4
+ import { preparePicoClawRuntimeAuth } from "./runAuth.js";
5
+ import { createPicoClawAgentScaffold } from "./scaffold.js";
6
+ import { assertSupportedPicoClawSurfaces, buildPicoClawChannelConfig, buildPicoClawSurfaceEnvBindings } from "./surfaces.js";
7
+ const formatModelName = (node) => {
8
+ const primary = node.execution?.model?.primary;
9
+ if (!primary)
10
+ return null;
11
+ return primary.name;
12
+ };
13
+ const resolveDefaultTemperature = (node) => {
14
+ const primary = node.execution?.model?.primary;
15
+ if (!primary)
16
+ return null;
17
+ if (primary.provider === "openai" && primary.name === "gpt-5") {
18
+ return 1;
19
+ }
20
+ return null;
21
+ };
22
+ const MODEL_PROVIDER_ENV_VARS = new Map([
23
+ ["anthropic", "ANTHROPIC_API_KEY"],
24
+ ["google", "GOOGLE_API_KEY"],
25
+ ["groq", "GROQ_API_KEY"],
26
+ ["mistral", "MISTRAL_API_KEY"],
27
+ ["openai", "OPENAI_API_KEY"],
28
+ ["openrouter", "OPENROUTER_API_KEY"],
29
+ ["xai", "XAI_API_KEY"]
30
+ ]);
31
+ const formatProviderEnvName = (provider) => MODEL_PROVIDER_ENV_VARS.get(provider) ??
32
+ `${provider.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_API_KEY`;
33
+ const createProviderSecretPath = (provider) => createSecretPath(formatProviderEnvName(provider));
34
+ const createSecretPath = (envName) => `secrets/${envName}`;
35
+ const buildModelList = (node) => {
36
+ return listEffectiveExecutionModelTargets(node.execution).map((target) => {
37
+ if (target.endpoint) {
38
+ return {
39
+ ...(target.auth.method === "api_key"
40
+ ? { api_key: `file://${createSecretPath(target.auth.key)}` }
41
+ : {}),
42
+ api_base: target.endpoint.base_url,
43
+ model: target.endpoint.compatibility === "anthropic"
44
+ ? `anthropic-messages/${target.name}`
45
+ : `openai/${target.name}`,
46
+ model_name: target.name
47
+ };
48
+ }
49
+ return {
50
+ ...(target.auth.method === "api_key"
51
+ ? {
52
+ api_key: `file://${createSecretPath(target.auth.key ?? resolveModelProviderEnvName(target.provider))}`
53
+ }
54
+ : {}),
55
+ model: `${target.provider}/${target.name}`,
56
+ model_name: target.name
57
+ };
58
+ });
59
+ };
60
+ const buildMcpServers = (servers) => {
61
+ const result = {};
62
+ for (const server of servers) {
63
+ const entry = { enabled: true };
64
+ if (server.transport === "stdio") {
65
+ entry.command = server.command;
66
+ if (server.args)
67
+ entry.args = server.args;
68
+ if (server.env)
69
+ entry.env = server.env;
70
+ }
71
+ else {
72
+ entry.type = server.transport === "streamable_http" ? "http" : server.transport;
73
+ entry.url = server.url;
74
+ }
75
+ if (server.auth) {
76
+ entry.headers = { [server.auth.secret]: "" };
77
+ }
78
+ result[server.name] = entry;
79
+ }
80
+ return result;
81
+ };
82
+ const buildPicoClawConfig = (node) => {
83
+ const modelName = formatModelName(node);
84
+ const restrictToWorkspace = node.runtime.options.restrict_to_workspace ?? true;
85
+ const temperature = resolveDefaultTemperature(node);
86
+ const config = {
87
+ agents: {
88
+ defaults: {
89
+ workspace: "<workspace-path>",
90
+ restrict_to_workspace: restrictToWorkspace,
91
+ ...(modelName ? { model_name: modelName } : {}),
92
+ ...(temperature !== null ? { temperature } : {})
93
+ }
94
+ },
95
+ model_list: buildModelList(node)
96
+ };
97
+ const channels = buildPicoClawChannelConfig(node.surfaces);
98
+ if (Object.keys(channels).length > 0) {
99
+ config.channels = channels;
100
+ }
101
+ if (node.mcpServers.length > 0) {
102
+ config.tools = {
103
+ mcp: {
104
+ enabled: true,
105
+ servers: buildMcpServers(node.mcpServers)
106
+ }
107
+ };
108
+ }
109
+ return `${JSON.stringify(config, null, 2)}\n`;
110
+ };
111
+ const createContainerTargets = async (inputs) => inputs.map((input) => {
112
+ const agent = input.value;
113
+ const envFiles = listExecutionModelSecretNames(agent.execution).map((secretName) => ({
114
+ envName: secretName,
115
+ relativePath: createSecretPath(secretName)
116
+ }));
117
+ const configEnvBindings = buildPicoClawSurfaceEnvBindings(agent.surfaces);
118
+ return {
119
+ configEnvBindings,
120
+ envFiles,
121
+ files: input.emittedFiles,
122
+ id: `${input.kind}-${input.slug}`,
123
+ sourceIds: [input.id]
124
+ };
125
+ });
126
+ export const picoClawAdapter = {
127
+ assertSupportedModelTarget(target) {
128
+ if (target.endpoint) {
129
+ if (target.endpoint.compatibility === "anthropic" &&
130
+ target.auth.method === "none") {
131
+ throw new SpawnfileError("validation_error", "PicoClaw anthropic-compatible custom or local models require api_key auth");
132
+ }
133
+ if (target.auth.method === "claude-code" || target.auth.method === "codex") {
134
+ throw new SpawnfileError("validation_error", `PicoClaw custom or local models do not support ${target.auth.method} auth`);
135
+ }
136
+ return;
137
+ }
138
+ if (target.provider === "anthropic") {
139
+ if (target.auth.method === "api_key" || target.auth.method === "claude-code") {
140
+ return;
141
+ }
142
+ }
143
+ else if (target.provider === "openai") {
144
+ if (target.auth.method === "api_key" || target.auth.method === "codex") {
145
+ return;
146
+ }
147
+ }
148
+ else if (target.auth.method === "api_key" || target.auth.method === "none") {
149
+ return;
150
+ }
151
+ throw new SpawnfileError("validation_error", `PicoClaw does not support model auth method ${target.auth.method} for provider ${target.provider}`);
152
+ },
153
+ assertSupportedSurfaces(surfaces) {
154
+ assertSupportedPicoClawSurfaces(surfaces);
155
+ },
156
+ container: {
157
+ configFileName: "config.json",
158
+ configPathEnv: "PICOCLAW_CONFIG",
159
+ globalNpmPackages: ["@anthropic-ai/claude-code", "@openai/codex"],
160
+ homeEnv: "PICOCLAW_HOME",
161
+ instancePaths: {
162
+ configPathTemplate: "<instance-root>/picoclaw/<config-file>",
163
+ homePathTemplate: "<instance-root>/picoclaw",
164
+ workspacePathTemplate: "<instance-root>/picoclaw/workspace"
165
+ },
166
+ port: 18790,
167
+ portEnv: "PICOCLAW_GATEWAY_PORT",
168
+ standaloneBaseImage: "debian:bookworm-slim",
169
+ startCommand: ["picoclaw", "gateway", "--allow-empty"],
170
+ staticEnv: {
171
+ PICOCLAW_GATEWAY_HOST: "0.0.0.0"
172
+ },
173
+ systemDeps: ["bash", "ca-certificates", "curl", "nodejs", "npm", "tar"]
174
+ },
175
+ async compileAgent(node) {
176
+ return {
177
+ capabilities: createAgentCapabilities(node),
178
+ diagnostics: [],
179
+ files: [
180
+ ...createDocumentFiles("workspace", node.docs),
181
+ ...createSkillFiles("workspace/skills", node.skills),
182
+ {
183
+ content: buildPicoClawConfig(node),
184
+ path: "config.json"
185
+ }
186
+ ]
187
+ };
188
+ },
189
+ async createContainerTargets(inputs) {
190
+ return createContainerTargets(inputs);
191
+ },
192
+ name: "picoclaw",
193
+ prepareRuntimeAuth: preparePicoClawRuntimeAuth,
194
+ scaffoldAgentProject: createPicoClawAgentScaffold,
195
+ validateRuntimeOptions(options) {
196
+ if ("restrict_to_workspace" in options &&
197
+ typeof options.restrict_to_workspace !== "boolean") {
198
+ return [
199
+ createDiagnostic("error", "PicoClaw runtime option restrict_to_workspace must be a boolean")
200
+ ];
201
+ }
202
+ return [];
203
+ }
204
+ };
@@ -0,0 +1,2 @@
1
+ import type { RuntimeAuthPreparationInput, RuntimeAuthPreparationResult } from "../types.js";
2
+ export declare const preparePicoClawRuntimeAuth: (input: RuntimeAuthPreparationInput) => Promise<RuntimeAuthPreparationResult>;
@@ -0,0 +1,117 @@
1
+ import path from "node:path";
2
+ import { loadImportedClaudeCodeCredential, loadImportedCodexCredential } from "../../auth/index.js";
3
+ import { copyDirectory, ensureDirectory, readUtf8File, writeUtf8File } from "../../filesystem/index.js";
4
+ const resolveRootfsSourcePath = (outputDirectory, containerPath) => path.join(outputDirectory, "container", "rootfs", ...path.posix.relative("/", containerPath).split("/"));
5
+ const createMountArgs = (hostPath, containerPath) => [
6
+ "-v",
7
+ `${hostPath}:${containerPath}`
8
+ ];
9
+ const createMountedHomeDirectory = async (input, patchedConfig, authStore) => {
10
+ const sourceHomePath = resolveRootfsSourcePath(input.outputDirectory, input.instance.home_path);
11
+ const mountedHomePath = path.join("runtime-auth", "picoclaw", input.instance.id, "home");
12
+ const hostHomePath = path.join(input.tempRoot, mountedHomePath);
13
+ await ensureDirectory(path.dirname(hostHomePath));
14
+ await copyDirectory(sourceHomePath, hostHomePath);
15
+ const relativeConfigPath = path.posix.relative(input.instance.home_path, input.instance.config_path);
16
+ const hostConfigPath = path.join(hostHomePath, ...relativeConfigPath.split("/"));
17
+ const hostAuthPath = path.join(hostHomePath, "auth.json");
18
+ await ensureDirectory(path.dirname(hostConfigPath));
19
+ await writeUtf8File(hostConfigPath, `${JSON.stringify(patchedConfig, null, 2)}\n`);
20
+ if (authStore) {
21
+ await writeUtf8File(hostAuthPath, `${JSON.stringify(authStore, null, 2)}\n`);
22
+ }
23
+ return hostHomePath;
24
+ };
25
+ const createPicoClawCredential = (provider, credential) => ({
26
+ access_token: credential.access,
27
+ ...("type" in credential && credential.type === "oauth" && credential.refresh
28
+ ? { refresh_token: credential.refresh }
29
+ : !("type" in credential)
30
+ ? { refresh_token: credential.refresh }
31
+ : {}),
32
+ ...("accountId" in credential && credential.accountId ? { account_id: credential.accountId } : {}),
33
+ auth_method: "oauth",
34
+ expires_at: new Date(credential.expires).toISOString(),
35
+ provider
36
+ });
37
+ const normalizeClaudeCliModelName = (modelName) => modelName.replaceAll(".", "-");
38
+ const patchPicoClawConfig = (config, options) => {
39
+ const agents = config.agents ?? {};
40
+ const defaults = agents.defaults ?? {};
41
+ const providers = (config.providers ?? {});
42
+ const modelList = Array.isArray(config.model_list) ? config.model_list : [];
43
+ const nextProviders = { ...providers };
44
+ const nextModelList = modelList.map((entry) => {
45
+ if (!entry || typeof entry !== "object") {
46
+ return entry;
47
+ }
48
+ const record = { ...entry };
49
+ const model = record.model;
50
+ const modelName = typeof record.model_name === "string" ? record.model_name : null;
51
+ if (options.useClaudeCode && typeof model === "string" && model.startsWith("anthropic/")) {
52
+ delete record.api_key;
53
+ delete record.auth_method;
54
+ if (modelName) {
55
+ record.model = `claude-cli/${normalizeClaudeCliModelName(modelName)}`;
56
+ }
57
+ }
58
+ if (options.useCodex && typeof model === "string" && model.startsWith("openai/")) {
59
+ delete record.api_key;
60
+ record.auth_method = "oauth";
61
+ }
62
+ return record;
63
+ });
64
+ if (options.useClaudeCode) {
65
+ delete nextProviders.anthropic;
66
+ }
67
+ if (options.useCodex) {
68
+ nextProviders.openai = {
69
+ ...(nextProviders.openai ?? {}),
70
+ auth_method: "oauth"
71
+ };
72
+ }
73
+ return {
74
+ ...config,
75
+ agents: {
76
+ ...agents,
77
+ defaults
78
+ },
79
+ model_list: nextModelList,
80
+ providers: nextProviders
81
+ };
82
+ };
83
+ export const preparePicoClawRuntimeAuth = async (input) => {
84
+ if (!input.instance.home_path) {
85
+ return { coveredModelSecrets: [], mountArgs: [] };
86
+ }
87
+ const claudeCode = input.authProfile.imports["claude-code"]
88
+ ? await loadImportedClaudeCodeCredential(input.authProfile.imports["claude-code"].path)
89
+ : null;
90
+ const codex = input.authProfile.imports.codex
91
+ ? await loadImportedCodexCredential(input.authProfile.imports.codex.path)
92
+ : null;
93
+ const useClaudeCode = input.instance.model_auth_methods.anthropic === "claude-code" && claudeCode;
94
+ const useCodex = input.instance.model_auth_methods.openai === "codex" && codex;
95
+ if (!useClaudeCode && !useCodex) {
96
+ return { coveredModelSecrets: [], mountArgs: [] };
97
+ }
98
+ const credentials = {};
99
+ const coveredModelSecrets = [];
100
+ if (useCodex) {
101
+ credentials.openai = createPicoClawCredential("openai", codex);
102
+ coveredModelSecrets.push("OPENAI_API_KEY");
103
+ }
104
+ if (useClaudeCode) {
105
+ coveredModelSecrets.push("ANTHROPIC_API_KEY");
106
+ }
107
+ const sourceConfig = JSON.parse(await readUtf8File(resolveRootfsSourcePath(input.outputDirectory, input.instance.config_path)));
108
+ const patchedConfig = patchPicoClawConfig(sourceConfig, {
109
+ useClaudeCode: Boolean(useClaudeCode),
110
+ useCodex: Boolean(useCodex)
111
+ });
112
+ const mountedHomePath = await createMountedHomeDirectory(input, patchedConfig, Object.keys(credentials).length > 0 ? { credentials } : null);
113
+ return {
114
+ coveredModelSecrets,
115
+ mountArgs: createMountArgs(mountedHomePath, input.instance.home_path)
116
+ };
117
+ };
@@ -0,0 +1,3 @@
1
+ # AGENTS.md
2
+
3
+ PicoClaw reads this from the workspace.
@@ -0,0 +1,5 @@
1
+ # PicoClaw Scaffold Assets
2
+
3
+ This folder contains the bundled markdown starter files used by `spawnfile init --runtime picoclaw`.
4
+
5
+ These files are copied into `dist/runtime/picoclaw/scaffold-assets/` during `npm run build` so the installed CLI can scaffold projects without depending on `blueprints/` or `runtimes/`.
@@ -0,0 +1,3 @@
1
+ # IDENTITY.md
2
+
3
+ PicoClaw reads this from the workspace.
@@ -0,0 +1,3 @@
1
+ # SOUL.md
2
+
3
+ PicoClaw reads this from the workspace.
@@ -0,0 +1,2 @@
1
+ import type { RuntimeAgentScaffold } from "../types.js";
2
+ export declare const createPicoClawAgentScaffold: () => RuntimeAgentScaffold;
@@ -0,0 +1,28 @@
1
+ import { createAgentScaffoldManifest } from "../../manifest/index.js";
2
+ import { loadRuntimeScaffoldAsset } from "../scaffoldAssets.js";
3
+ export const createPicoClawAgentScaffold = () => ({
4
+ files: [
5
+ {
6
+ content: loadRuntimeScaffoldAsset(import.meta.url, "IDENTITY.md"),
7
+ path: "IDENTITY.md"
8
+ },
9
+ {
10
+ content: loadRuntimeScaffoldAsset(import.meta.url, "SOUL.md"),
11
+ path: "SOUL.md"
12
+ },
13
+ {
14
+ content: loadRuntimeScaffoldAsset(import.meta.url, "AGENTS.md"),
15
+ path: "AGENTS.md"
16
+ }
17
+ ],
18
+ manifest: createAgentScaffoldManifest({
19
+ docs: {
20
+ identity: "IDENTITY.md",
21
+ soul: "SOUL.md",
22
+ system: "AGENTS.md"
23
+ },
24
+ modelName: "claude-sonnet-4-6",
25
+ provider: "anthropic",
26
+ runtime: "picoclaw"
27
+ })
28
+ });
@@ -0,0 +1,5 @@
1
+ import type { ResolvedAgentSurfaces } from "../../compiler/types.js";
2
+ import type { RuntimeContainerConfigEnvBinding } from "../types.js";
3
+ export declare const buildPicoClawChannelConfig: (surfaces: ResolvedAgentSurfaces | undefined) => Record<string, unknown>;
4
+ export declare const buildPicoClawSurfaceEnvBindings: (surfaces: ResolvedAgentSurfaces | undefined) => RuntimeContainerConfigEnvBinding[] | undefined;
5
+ export declare const assertSupportedPicoClawSurfaces: (surfaces: ResolvedAgentSurfaces | undefined) => void;
@@ -0,0 +1,111 @@
1
+ import { SpawnfileError } from "../../shared/index.js";
2
+ export const buildPicoClawChannelConfig = (surfaces) => {
3
+ if (!surfaces) {
4
+ return {};
5
+ }
6
+ const channels = {};
7
+ if (surfaces.discord) {
8
+ channels.discord = {
9
+ ...(surfaces.discord.access?.mode === "allowlist"
10
+ ? { allow_from: surfaces.discord.access.users }
11
+ : {}),
12
+ enabled: true,
13
+ mention_only: true
14
+ };
15
+ }
16
+ if (surfaces.telegram) {
17
+ channels.telegram = {
18
+ ...(surfaces.telegram.access?.mode === "allowlist"
19
+ ? { allow_from: surfaces.telegram.access.users }
20
+ : {}),
21
+ enabled: true
22
+ };
23
+ }
24
+ if (surfaces.whatsapp) {
25
+ channels.whatsapp = {
26
+ ...(surfaces.whatsapp.access?.mode === "allowlist"
27
+ ? { allow_from: surfaces.whatsapp.access.users }
28
+ : {}),
29
+ enabled: true,
30
+ use_native: true
31
+ };
32
+ }
33
+ if (surfaces.slack) {
34
+ channels.slack = {
35
+ ...(surfaces.slack.access?.mode === "allowlist"
36
+ ? { allow_from: surfaces.slack.access.users }
37
+ : {}),
38
+ enabled: true,
39
+ group_trigger: {
40
+ mention_only: true
41
+ }
42
+ };
43
+ }
44
+ return channels;
45
+ };
46
+ export const buildPicoClawSurfaceEnvBindings = (surfaces) => {
47
+ if (!surfaces) {
48
+ return undefined;
49
+ }
50
+ const bindings = [];
51
+ if (surfaces.discord) {
52
+ bindings.push({
53
+ envName: surfaces.discord.botTokenSecret,
54
+ jsonPath: "channels.discord.token"
55
+ });
56
+ }
57
+ if (surfaces.telegram) {
58
+ bindings.push({
59
+ envName: surfaces.telegram.botTokenSecret,
60
+ jsonPath: "channels.telegram.token"
61
+ });
62
+ }
63
+ if (surfaces.slack) {
64
+ bindings.push({
65
+ envName: surfaces.slack.botTokenSecret,
66
+ jsonPath: "channels.slack.bot_token"
67
+ }, {
68
+ envName: surfaces.slack.appTokenSecret,
69
+ jsonPath: "channels.slack.app_token"
70
+ });
71
+ }
72
+ return bindings.length > 0 ? bindings : undefined;
73
+ };
74
+ export const assertSupportedPicoClawSurfaces = (surfaces) => {
75
+ const discordAccess = surfaces?.discord?.access;
76
+ if (discordAccess) {
77
+ if (discordAccess.mode === "pairing") {
78
+ throw new SpawnfileError("validation_error", "PicoClaw Discord does not support pairing access");
79
+ }
80
+ if (discordAccess.guilds.length > 0 || discordAccess.channels.length > 0) {
81
+ throw new SpawnfileError("validation_error", "PicoClaw Discord only supports user allowlists in Spawnfile v0.1");
82
+ }
83
+ }
84
+ const telegramAccess = surfaces?.telegram?.access;
85
+ if (telegramAccess) {
86
+ if (telegramAccess.mode === "pairing") {
87
+ throw new SpawnfileError("validation_error", "PicoClaw Telegram does not support pairing access");
88
+ }
89
+ if (telegramAccess.chats.length > 0) {
90
+ throw new SpawnfileError("validation_error", "PicoClaw Telegram only supports user allowlists in Spawnfile v0.1");
91
+ }
92
+ }
93
+ const whatsappAccess = surfaces?.whatsapp?.access;
94
+ if (whatsappAccess) {
95
+ if (whatsappAccess.mode === "pairing") {
96
+ throw new SpawnfileError("validation_error", "PicoClaw WhatsApp does not support pairing access");
97
+ }
98
+ if (whatsappAccess.groups.length > 0) {
99
+ throw new SpawnfileError("validation_error", "PicoClaw WhatsApp only supports user allowlists in Spawnfile v0.1");
100
+ }
101
+ }
102
+ const slackAccess = surfaces?.slack?.access;
103
+ if (slackAccess) {
104
+ if (slackAccess.mode === "pairing") {
105
+ throw new SpawnfileError("validation_error", "PicoClaw Slack does not support pairing access");
106
+ }
107
+ if (slackAccess.channels.length > 0) {
108
+ throw new SpawnfileError("validation_error", "PicoClaw Slack only supports user allowlists in Spawnfile v0.1");
109
+ }
110
+ }
111
+ };
@@ -0,0 +1,41 @@
1
+ import type { DiagnosticReport } from "../report/index.js";
2
+ import type { RuntimeLifecycleStatus } from "../shared/index.js";
3
+ import type { RuntimeAdapter } from "./types.js";
4
+ export type RuntimeRegistryInstall = {
5
+ image: string;
6
+ kind: "container_image";
7
+ tag: string;
8
+ } | {
9
+ kind: "github_release_archive";
10
+ assets: Record<string, string>;
11
+ binary: string;
12
+ repository: string;
13
+ tag: string;
14
+ } | {
15
+ kind: "github_release_bundle";
16
+ asset: string;
17
+ repository: string;
18
+ tag: string;
19
+ } | {
20
+ kind: "npm";
21
+ package: string;
22
+ version: string;
23
+ } | {
24
+ kind: "source_repo";
25
+ };
26
+ export interface RuntimeRegistryEntry {
27
+ defaultBranch: string;
28
+ install?: RuntimeRegistryInstall;
29
+ name: string;
30
+ ref: string;
31
+ remote: string;
32
+ status: RuntimeLifecycleStatus;
33
+ }
34
+ export declare const parseRuntimeRegistry: (source: string) => RuntimeRegistryEntry[];
35
+ export declare const loadRuntimeRegistry: () => Promise<RuntimeRegistryEntry[]>;
36
+ export declare const getRegisteredRuntime: (runtimeName: string) => Promise<RuntimeRegistryEntry | undefined>;
37
+ export declare const hasRuntimeAdapter: (runtimeName: string) => boolean;
38
+ export declare const assertRuntimeCanCompile: (runtimeName: string) => Promise<RuntimeRegistryEntry>;
39
+ export declare const createRuntimeLifecycleDiagnostics: (runtime: Pick<RuntimeRegistryEntry, "name" | "ref" | "status">) => DiagnosticReport[];
40
+ export declare const getRuntimeAdapter: (runtimeName: string) => RuntimeAdapter;
41
+ export declare const listRuntimeAdapters: () => string[];
@@ -0,0 +1,134 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { parse as parseYaml } from "yaml";
3
+ import { z } from "zod";
4
+ import { SpawnfileError } from "../shared/index.js";
5
+ import { openClawAdapter } from "./openclaw/adapter.js";
6
+ import { picoClawAdapter } from "./picoclaw/adapter.js";
7
+ import { tinyClawAdapter } from "./tinyclaw/adapter.js";
8
+ const runtimeAdapters = new Map([
9
+ [openClawAdapter.name, openClawAdapter],
10
+ [picoClawAdapter.name, picoClawAdapter],
11
+ [tinyClawAdapter.name, tinyClawAdapter]
12
+ ]);
13
+ const runtimeInstallSchema = z.discriminatedUnion("kind", [
14
+ z
15
+ .object({
16
+ image: z.string().min(1),
17
+ kind: z.literal("container_image"),
18
+ tag: z.string().min(1)
19
+ })
20
+ .strict(),
21
+ z
22
+ .object({
23
+ kind: z.literal("npm"),
24
+ package: z.string().min(1),
25
+ version: z.string().min(1)
26
+ })
27
+ .strict(),
28
+ z
29
+ .object({
30
+ assets: z.record(z.string(), z.string().min(1)),
31
+ binary: z.string().min(1),
32
+ kind: z.literal("github_release_archive"),
33
+ repository: z.string().min(1),
34
+ tag: z.string().min(1)
35
+ })
36
+ .strict(),
37
+ z
38
+ .object({
39
+ asset: z.string().min(1),
40
+ kind: z.literal("github_release_bundle"),
41
+ repository: z.string().min(1),
42
+ tag: z.string().min(1)
43
+ })
44
+ .strict(),
45
+ z
46
+ .object({
47
+ kind: z.literal("source_repo")
48
+ })
49
+ .strict()
50
+ ]);
51
+ const runtimeRegistrySchema = z
52
+ .object({
53
+ runtimes: z.record(z.string(), z
54
+ .object({
55
+ default_branch: z.string().min(1),
56
+ install: runtimeInstallSchema.optional(),
57
+ ref: z.string().min(1),
58
+ remote: z.string().min(1),
59
+ status: z.enum(["active", "deprecated", "exploratory"])
60
+ })
61
+ .strict())
62
+ })
63
+ .strict();
64
+ const runtimeRegistryUrl = new URL("../../runtimes.yaml", import.meta.url);
65
+ let runtimeRegistryPromise;
66
+ export const parseRuntimeRegistry = (source) => {
67
+ try {
68
+ const parsed = runtimeRegistrySchema.parse(parseYaml(source));
69
+ return Object.entries(parsed.runtimes)
70
+ .map(([name, entry]) => ({
71
+ defaultBranch: entry.default_branch,
72
+ install: entry.install,
73
+ name,
74
+ ref: entry.ref,
75
+ remote: entry.remote,
76
+ status: entry.status
77
+ }))
78
+ .sort((left, right) => left.name.localeCompare(right.name));
79
+ }
80
+ catch (error) {
81
+ if (error instanceof z.ZodError) {
82
+ const issue = error.issues[0];
83
+ throw new SpawnfileError("runtime_error", `Invalid runtime registry: ${issue.message}`);
84
+ }
85
+ throw error;
86
+ }
87
+ };
88
+ const readRuntimeRegistry = async () => {
89
+ try {
90
+ const source = await readFile(runtimeRegistryUrl, "utf8");
91
+ return parseRuntimeRegistry(source);
92
+ }
93
+ catch (error) {
94
+ if (error instanceof SpawnfileError) {
95
+ throw error;
96
+ }
97
+ throw new SpawnfileError("runtime_error", `Unable to read runtime registry at ${runtimeRegistryUrl.pathname}`);
98
+ }
99
+ };
100
+ export const loadRuntimeRegistry = async () => {
101
+ runtimeRegistryPromise ??= readRuntimeRegistry();
102
+ return runtimeRegistryPromise;
103
+ };
104
+ export const getRegisteredRuntime = async (runtimeName) => (await loadRuntimeRegistry()).find((entry) => entry.name === runtimeName);
105
+ export const hasRuntimeAdapter = (runtimeName) => runtimeAdapters.has(runtimeName);
106
+ export const assertRuntimeCanCompile = async (runtimeName) => {
107
+ const registeredRuntime = await getRegisteredRuntime(runtimeName);
108
+ if (!registeredRuntime) {
109
+ throw new SpawnfileError("runtime_error", `Unknown runtime binding: ${runtimeName}`);
110
+ }
111
+ if (registeredRuntime.status === "exploratory") {
112
+ throw new SpawnfileError("runtime_error", `Runtime ${runtimeName} is exploratory and cannot be compiled in v0.1`);
113
+ }
114
+ if (!hasRuntimeAdapter(runtimeName)) {
115
+ throw new SpawnfileError("runtime_error", `Runtime ${runtimeName} is marked ${registeredRuntime.status} in runtimes.yaml but has no bundled adapter`);
116
+ }
117
+ return registeredRuntime;
118
+ };
119
+ export const createRuntimeLifecycleDiagnostics = (runtime) => runtime.status === "deprecated"
120
+ ? [
121
+ {
122
+ level: "warn",
123
+ message: `Runtime ${runtime.name} is deprecated in Spawnfile and pinned at ${runtime.ref}`
124
+ }
125
+ ]
126
+ : [];
127
+ export const getRuntimeAdapter = (runtimeName) => {
128
+ const adapter = runtimeAdapters.get(runtimeName);
129
+ if (!adapter) {
130
+ throw new SpawnfileError("runtime_error", `Unknown runtime adapter: ${runtimeName}`);
131
+ }
132
+ return adapter;
133
+ };
134
+ export const listRuntimeAdapters = () => [...runtimeAdapters.keys()].sort();
@@ -0,0 +1 @@
1
+ export declare const loadRuntimeScaffoldAsset: (moduleUrl: string, assetName: string) => string;
@@ -0,0 +1,2 @@
1
+ import { readFileSync } from "node:fs";
2
+ export const loadRuntimeScaffoldAsset = (moduleUrl, assetName) => readFileSync(new URL(`./scaffold-assets/${assetName}`, moduleUrl), "utf8");