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,125 @@
1
+ import path from "node:path";
2
+ import { z } from "zod";
3
+ import { ensureDirectory, fileExists, readUtf8File, removeDirectory, writeUtf8File } from "../filesystem/index.js";
4
+ import { SpawnfileError } from "../shared/index.js";
5
+ import { resolveAuthHome, resolveImportedAuthDirectory, resolveProfileDirectory, resolveProfilePath } from "./paths.js";
6
+ const importedAuthEntrySchema = z.object({
7
+ kind: z.enum(["claude-code", "codex"]),
8
+ relativePath: z.string().min(1)
9
+ });
10
+ const authProfileSchema = z.object({
11
+ env: z.record(z.string(), z.string()).default({}),
12
+ imports: z
13
+ .object({
14
+ "claude-code": importedAuthEntrySchema.optional(),
15
+ codex: importedAuthEntrySchema.optional()
16
+ })
17
+ .default({}),
18
+ version: z.literal(1)
19
+ });
20
+ const createEmptyProfile = () => ({
21
+ env: {},
22
+ imports: {},
23
+ version: 1
24
+ });
25
+ export const createResolvedAuthProfile = (profileName, profile) => ({
26
+ authHome: resolveAuthHome(),
27
+ env: { ...profile.env },
28
+ imports: Object.fromEntries(Object.entries(profile.imports).map(([kind, entry]) => [
29
+ kind,
30
+ {
31
+ kind: entry.kind,
32
+ path: path.join(resolveProfileDirectory(profileName), entry.relativePath)
33
+ }
34
+ ])),
35
+ name: profileName,
36
+ profileDirectory: resolveProfileDirectory(profileName),
37
+ profilePath: resolveProfilePath(profileName),
38
+ version: 1
39
+ });
40
+ export const loadAuthProfile = async (profileName) => {
41
+ const profilePath = resolveProfilePath(profileName);
42
+ if (!(await fileExists(profilePath))) {
43
+ return null;
44
+ }
45
+ const fileContent = await readUtf8File(profilePath);
46
+ let raw;
47
+ try {
48
+ raw = JSON.parse(fileContent);
49
+ }
50
+ catch (error) {
51
+ throw new SpawnfileError("validation_error", `Invalid auth profile ${profileName}: ${error instanceof Error ? error.message : String(error)}`);
52
+ }
53
+ const parsed = authProfileSchema.safeParse(raw);
54
+ if (!parsed.success) {
55
+ throw new SpawnfileError("validation_error", `Invalid auth profile ${profileName}: ${parsed.error.issues[0]?.message ?? "unknown error"}`);
56
+ }
57
+ return createResolvedAuthProfile(profileName, parsed.data);
58
+ };
59
+ export const requireAuthProfile = async (profileName) => {
60
+ const profile = await loadAuthProfile(profileName);
61
+ if (profile) {
62
+ return profile;
63
+ }
64
+ throw new SpawnfileError("validation_error", `Auth profile does not exist: ${profileName}`);
65
+ };
66
+ export const ensureAuthProfile = async (profileName) => {
67
+ const existing = await loadAuthProfile(profileName);
68
+ if (existing) {
69
+ return existing;
70
+ }
71
+ const profileDirectory = resolveProfileDirectory(profileName);
72
+ await ensureDirectory(profileDirectory);
73
+ await writeUtf8File(resolveProfilePath(profileName), `${JSON.stringify(createEmptyProfile(), null, 2)}\n`);
74
+ return createResolvedAuthProfile(profileName, createEmptyProfile());
75
+ };
76
+ const writeProfile = async (profileName, profile) => {
77
+ const profileDirectory = resolveProfileDirectory(profileName);
78
+ await ensureDirectory(profileDirectory);
79
+ await writeUtf8File(resolveProfilePath(profileName), `${JSON.stringify(profile, null, 2)}\n`);
80
+ return createResolvedAuthProfile(profileName, profile);
81
+ };
82
+ export const setAuthProfileEnv = async (profileName, env) => {
83
+ const current = (await loadAuthProfile(profileName)) ?? (await ensureAuthProfile(profileName));
84
+ return writeProfile(profileName, {
85
+ env: {
86
+ ...current.env,
87
+ ...env
88
+ },
89
+ imports: Object.fromEntries(Object.entries(current.imports).map(([kind, entry]) => [
90
+ kind,
91
+ {
92
+ kind: entry.kind,
93
+ relativePath: path.relative(current.profileDirectory, entry.path)
94
+ }
95
+ ])),
96
+ version: 1
97
+ });
98
+ };
99
+ export const registerImportedAuth = async (profileName, kind) => {
100
+ const current = (await loadAuthProfile(profileName)) ?? (await ensureAuthProfile(profileName));
101
+ const importDirectory = resolveImportedAuthDirectory(profileName, kind);
102
+ await removeDirectory(importDirectory);
103
+ await ensureDirectory(importDirectory);
104
+ const nextProfile = {
105
+ env: { ...current.env },
106
+ imports: {
107
+ ...Object.fromEntries(Object.entries(current.imports).map(([entryKind, entry]) => [
108
+ entryKind,
109
+ {
110
+ kind: entry.kind,
111
+ relativePath: path.relative(current.profileDirectory, entry.path)
112
+ }
113
+ ])),
114
+ [kind]: {
115
+ kind,
116
+ relativePath: path.relative(current.profileDirectory, importDirectory)
117
+ }
118
+ },
119
+ version: 1
120
+ };
121
+ return {
122
+ directory: importDirectory,
123
+ profile: await writeProfile(profileName, nextProfile)
124
+ };
125
+ };
@@ -0,0 +1,14 @@
1
+ export interface ClaudeCodeImportedCredential {
2
+ access: string;
3
+ expires: number;
4
+ refresh?: string;
5
+ type: "oauth" | "token";
6
+ }
7
+ export interface CodexImportedCredential {
8
+ access: string;
9
+ accountId?: string;
10
+ expires: number;
11
+ refresh: string;
12
+ }
13
+ export declare const loadImportedClaudeCodeCredential: (sourceDirectory: string) => Promise<ClaudeCodeImportedCredential | null>;
14
+ export declare const loadImportedCodexCredential: (sourceDirectory: string) => Promise<CodexImportedCredential | null>;
@@ -0,0 +1,76 @@
1
+ import path from "node:path";
2
+ import { stat } from "node:fs/promises";
3
+ import { fileExists, readUtf8File } from "../filesystem/index.js";
4
+ import { SpawnfileError } from "../shared/index.js";
5
+ const parseJson = (filePath, content) => {
6
+ try {
7
+ const parsed = JSON.parse(content);
8
+ if (!parsed || typeof parsed !== "object") {
9
+ throw new Error("top-level JSON value must be an object");
10
+ }
11
+ return parsed;
12
+ }
13
+ catch (error) {
14
+ throw new SpawnfileError("validation_error", `Invalid imported auth file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
15
+ }
16
+ };
17
+ export const loadImportedClaudeCodeCredential = async (sourceDirectory) => {
18
+ const credentialsPath = path.join(sourceDirectory, ".credentials.json");
19
+ if (!(await fileExists(credentialsPath))) {
20
+ return null;
21
+ }
22
+ const parsed = parseJson(credentialsPath, await readUtf8File(credentialsPath));
23
+ const oauth = parsed.claudeAiOauth;
24
+ if (!oauth || typeof oauth !== "object") {
25
+ return null;
26
+ }
27
+ const access = oauth.accessToken;
28
+ const refresh = oauth.refreshToken;
29
+ const expires = oauth.expiresAt;
30
+ if (typeof access !== "string" || access.length === 0) {
31
+ return null;
32
+ }
33
+ if (typeof expires !== "number" || !Number.isFinite(expires) || expires <= 0) {
34
+ return null;
35
+ }
36
+ if (typeof refresh === "string" && refresh.length > 0) {
37
+ return {
38
+ access,
39
+ expires,
40
+ refresh,
41
+ type: "oauth"
42
+ };
43
+ }
44
+ return {
45
+ access,
46
+ expires,
47
+ type: "token"
48
+ };
49
+ };
50
+ export const loadImportedCodexCredential = async (sourceDirectory) => {
51
+ const authPath = path.join(sourceDirectory, "auth.json");
52
+ if (!(await fileExists(authPath))) {
53
+ return null;
54
+ }
55
+ const parsed = parseJson(authPath, await readUtf8File(authPath));
56
+ const tokens = parsed.tokens;
57
+ if (!tokens || typeof tokens !== "object") {
58
+ return null;
59
+ }
60
+ const access = tokens.access_token;
61
+ const refresh = tokens.refresh_token;
62
+ const accountId = tokens.account_id;
63
+ if (typeof access !== "string" ||
64
+ access.length === 0 ||
65
+ typeof refresh !== "string" ||
66
+ refresh.length === 0) {
67
+ return null;
68
+ }
69
+ const fileStats = await stat(authPath);
70
+ return {
71
+ access,
72
+ ...(typeof accountId === "string" && accountId.length > 0 ? { accountId } : {}),
73
+ expires: fileStats.mtimeMs + 60 * 60 * 1000,
74
+ refresh
75
+ };
76
+ };
@@ -0,0 +1,22 @@
1
+ export type ImportedAuthKind = "claude-code" | "codex";
2
+ export interface ImportedAuthEntry {
3
+ kind: ImportedAuthKind;
4
+ relativePath: string;
5
+ }
6
+ export interface AuthProfile {
7
+ env: Record<string, string>;
8
+ imports: Partial<Record<ImportedAuthKind, ImportedAuthEntry>>;
9
+ version: 1;
10
+ }
11
+ export interface ResolvedAuthProfile {
12
+ authHome: string;
13
+ env: Record<string, string>;
14
+ imports: Partial<Record<ImportedAuthKind, {
15
+ kind: ImportedAuthKind;
16
+ path: string;
17
+ }>>;
18
+ name: string;
19
+ profileDirectory: string;
20
+ profilePath: string;
21
+ version: 1;
22
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from "./runCli.js";
3
+ const exitCode = await runCli(process.argv.slice(2));
4
+ process.exit(exitCode);
@@ -0,0 +1,27 @@
1
+ import { importClaudeCodeAuth, importCodexAuth, importEnvFile, requireAuthProfile } from "../auth/index.js";
2
+ import { addAgentProject, addProjectModelFallback, addSubagentProject, addTeamProject, buildCompilePlan, buildProject, clearProjectModelFallbacks, compileProject, initProject, runProject, setProjectPrimaryModel, syncProjectAuth } from "../compiler/index.js";
3
+ import { listRuntimeAdapters } from "../runtime/index.js";
4
+ export interface CliStreams {
5
+ stderr: (message: string) => void;
6
+ stdout: (message: string) => void;
7
+ }
8
+ export interface CliHandlers {
9
+ buildCompilePlan: typeof buildCompilePlan;
10
+ buildProject: typeof buildProject;
11
+ compileProject: typeof compileProject;
12
+ addAgentProject: typeof addAgentProject;
13
+ addProjectModelFallback: typeof addProjectModelFallback;
14
+ addSubagentProject: typeof addSubagentProject;
15
+ addTeamProject: typeof addTeamProject;
16
+ clearProjectModelFallbacks: typeof clearProjectModelFallbacks;
17
+ importClaudeCodeAuth: typeof importClaudeCodeAuth;
18
+ importCodexAuth: typeof importCodexAuth;
19
+ importEnvFile: typeof importEnvFile;
20
+ initProject: typeof initProject;
21
+ listRuntimeAdapters: typeof listRuntimeAdapters;
22
+ requireAuthProfile: typeof requireAuthProfile;
23
+ runProject: typeof runProject;
24
+ setProjectPrimaryModel: typeof setProjectPrimaryModel;
25
+ syncProjectAuth: typeof syncProjectAuth;
26
+ }
27
+ export declare const runCli: (argv: string[], streams?: CliStreams, handlerOverrides?: Partial<CliHandlers>) => Promise<number>;
@@ -0,0 +1,314 @@
1
+ import { Command } from "commander";
2
+ import { importClaudeCodeAuth, importCodexAuth, importEnvFile, requireAuthProfile } from "../auth/index.js";
3
+ import { addAgentProject, addProjectModelFallback, addSubagentProject, addTeamProject, buildCompilePlan, buildProject, clearProjectModelFallbacks, compileProject, initProject, runProject, setProjectPrimaryModel, syncProjectAuth } from "../compiler/index.js";
4
+ import { isSpawnfileError } from "../shared/index.js";
5
+ import { listRuntimeAdapters } from "../runtime/index.js";
6
+ const createDefaultStreams = () => ({
7
+ stderr: (message) => process.stderr.write(`${message}\n`),
8
+ stdout: (message) => process.stdout.write(`${message}\n`)
9
+ });
10
+ const createDefaultHandlers = () => ({
11
+ buildCompilePlan,
12
+ buildProject,
13
+ compileProject,
14
+ addAgentProject,
15
+ addProjectModelFallback,
16
+ addSubagentProject,
17
+ addTeamProject,
18
+ clearProjectModelFallbacks,
19
+ importClaudeCodeAuth,
20
+ importCodexAuth,
21
+ importEnvFile,
22
+ initProject,
23
+ listRuntimeAdapters,
24
+ requireAuthProfile,
25
+ runProject,
26
+ setProjectPrimaryModel,
27
+ syncProjectAuth
28
+ });
29
+ const formatPlanSummary = (plan) => [
30
+ `root: ${plan.root}`,
31
+ `nodes: ${plan.nodes.length}`,
32
+ `runtimes: ${Object.keys(plan.runtimes).sort().join(", ") || "none"}`
33
+ ].join("\n");
34
+ const formatAuthProfileSummary = (profile) => {
35
+ const envKeys = Object.keys(profile.env).sort();
36
+ const importedKinds = Object.keys(profile.imports).sort();
37
+ return [
38
+ `profile: ${profile.name}`,
39
+ `env: ${envKeys.length > 0 ? envKeys.join(", ") : "none"}`,
40
+ `imports: ${importedKinds.length > 0 ? importedKinds.join(", ") : "none"}`
41
+ ];
42
+ };
43
+ export const runCli = async (argv, streams = createDefaultStreams(), handlerOverrides = {}) => {
44
+ const handlers = { ...createDefaultHandlers(), ...handlerOverrides };
45
+ const program = new Command();
46
+ program.name("spawnfile").description("Spawnfile v0.1 compiler");
47
+ program
48
+ .command("compile")
49
+ .argument("[path]", "Project directory or Spawnfile path", process.cwd())
50
+ .option("-o, --out <directory>", "Output directory")
51
+ .action(async (inputPath, options) => {
52
+ const result = await handlers.compileProject(inputPath, { outputDirectory: options.out });
53
+ streams.stdout(`compiled to ${result.outputDirectory}`);
54
+ streams.stdout(`report: ${result.reportPath}`);
55
+ });
56
+ program
57
+ .command("build")
58
+ .argument("[path]", "Project directory or Spawnfile path", process.cwd())
59
+ .option("-o, --out <directory>", "Output directory")
60
+ .option("-t, --tag <image>", "Docker image tag")
61
+ .action(async (inputPath, options) => {
62
+ const result = await handlers.buildProject(inputPath, {
63
+ imageTag: options.tag,
64
+ outputDirectory: options.out
65
+ });
66
+ streams.stdout(`built image ${result.imageTag}`);
67
+ streams.stdout(`compiled to ${result.outputDirectory}`);
68
+ streams.stdout(`report: ${result.reportPath}`);
69
+ });
70
+ program
71
+ .command("run")
72
+ .argument("[path]", "Project directory or Spawnfile path", process.cwd())
73
+ .option("-o, --out <directory>", "Output directory")
74
+ .option("-t, --tag <image>", "Docker image tag")
75
+ .option("--auth-profile <name>", "Local Spawnfile auth profile")
76
+ .option("--name <container>", "Docker container name")
77
+ .option("-d, --detach", "Run the container in detached mode")
78
+ .action(async (inputPath, options) => {
79
+ const result = await handlers.runProject(inputPath, {
80
+ authProfile: options.authProfile,
81
+ containerName: options.name,
82
+ detach: options.detach,
83
+ imageTag: options.tag,
84
+ outputDirectory: options.out
85
+ });
86
+ if (options.detach) {
87
+ streams.stdout(`running container ${result.containerName ?? "unknown"}`);
88
+ streams.stdout(`image: ${result.imageTag}`);
89
+ }
90
+ });
91
+ program
92
+ .command("init")
93
+ .argument("[path]", "Directory to initialize", process.cwd())
94
+ .option("--team", "Initialize a team project")
95
+ .option("--runtime <name>", "Runtime for agent scaffolds")
96
+ .action(async (inputPath, options) => {
97
+ const result = await handlers.initProject({
98
+ directory: inputPath,
99
+ runtime: options.runtime,
100
+ team: options.team
101
+ });
102
+ streams.stdout(`initialized ${result.directory}`);
103
+ for (const filePath of result.createdFiles) {
104
+ streams.stdout(`created ${filePath}`);
105
+ }
106
+ });
107
+ const addCommand = program.command("add").description("Add children to an existing Spawnfile project");
108
+ addCommand
109
+ .command("agent")
110
+ .argument("<id>", "Agent member id")
111
+ .argument("[path]", "Team project directory or Spawnfile path", process.cwd())
112
+ .option("--runtime <name>", "Runtime for the new agent member")
113
+ .action(async (id, inputPath, options) => {
114
+ const result = await handlers.addAgentProject({
115
+ id,
116
+ path: inputPath,
117
+ runtime: options.runtime
118
+ });
119
+ for (const filePath of result.updatedFiles) {
120
+ streams.stdout(`updated ${filePath}`);
121
+ }
122
+ for (const filePath of result.createdFiles) {
123
+ streams.stdout(`created ${filePath}`);
124
+ }
125
+ });
126
+ addCommand
127
+ .command("subagent")
128
+ .argument("<id>", "Subagent id")
129
+ .argument("[path]", "Agent project directory or Spawnfile path", process.cwd())
130
+ .action(async (id, inputPath) => {
131
+ const result = await handlers.addSubagentProject({
132
+ id,
133
+ path: inputPath
134
+ });
135
+ for (const filePath of result.updatedFiles) {
136
+ streams.stdout(`updated ${filePath}`);
137
+ }
138
+ for (const filePath of result.createdFiles) {
139
+ streams.stdout(`created ${filePath}`);
140
+ }
141
+ });
142
+ addCommand
143
+ .command("team")
144
+ .argument("<id>", "Nested team id")
145
+ .argument("[path]", "Team project directory or Spawnfile path", process.cwd())
146
+ .action(async (id, inputPath) => {
147
+ const result = await handlers.addTeamProject({
148
+ id,
149
+ path: inputPath
150
+ });
151
+ for (const filePath of result.updatedFiles) {
152
+ streams.stdout(`updated ${filePath}`);
153
+ }
154
+ for (const filePath of result.createdFiles) {
155
+ streams.stdout(`created ${filePath}`);
156
+ }
157
+ });
158
+ const modelCommand = program
159
+ .command("model")
160
+ .description("Edit primary and fallback model declarations");
161
+ modelCommand
162
+ .command("set")
163
+ .argument("<provider>", "Model provider")
164
+ .argument("<name>", "Model name")
165
+ .argument("[path]", "Project directory or Spawnfile path", process.cwd())
166
+ .option("--auth <method>", "Model auth method")
167
+ .option("--key <env>", "Environment variable for api_key auth")
168
+ .option("--compat <compatibility>", "Endpoint compatibility for custom/local models")
169
+ .option("--base-url <url>", "Endpoint base URL for custom/local models")
170
+ .option("--recursive", "Update the target project and its descendants")
171
+ .action(async (provider, name, inputPath, options) => {
172
+ const result = await handlers.setProjectPrimaryModel({
173
+ authKey: options.key,
174
+ authMethod: options.auth,
175
+ endpointBaseUrl: options.baseUrl,
176
+ endpointCompatibility: options.compat,
177
+ name,
178
+ path: inputPath,
179
+ provider,
180
+ recursive: options.recursive
181
+ });
182
+ for (const filePath of result.updatedFiles) {
183
+ streams.stdout(`updated ${filePath}`);
184
+ }
185
+ });
186
+ modelCommand
187
+ .command("add-fallback")
188
+ .argument("<provider>", "Model provider")
189
+ .argument("<name>", "Model name")
190
+ .argument("[path]", "Project directory or Spawnfile path", process.cwd())
191
+ .option("--auth <method>", "Model auth method")
192
+ .option("--key <env>", "Environment variable for api_key auth")
193
+ .option("--compat <compatibility>", "Endpoint compatibility for custom/local models")
194
+ .option("--base-url <url>", "Endpoint base URL for custom/local models")
195
+ .option("--recursive", "Update the target project and its descendants")
196
+ .action(async (provider, name, inputPath, options) => {
197
+ const result = await handlers.addProjectModelFallback({
198
+ authKey: options.key,
199
+ authMethod: options.auth,
200
+ endpointBaseUrl: options.baseUrl,
201
+ endpointCompatibility: options.compat,
202
+ name,
203
+ path: inputPath,
204
+ provider,
205
+ recursive: options.recursive
206
+ });
207
+ for (const filePath of result.updatedFiles) {
208
+ streams.stdout(`updated ${filePath}`);
209
+ }
210
+ });
211
+ modelCommand
212
+ .command("clear-fallbacks")
213
+ .argument("[path]", "Project directory or Spawnfile path", process.cwd())
214
+ .option("--recursive", "Update the target project and its descendants")
215
+ .action(async (inputPath, options) => {
216
+ const result = await handlers.clearProjectModelFallbacks({
217
+ path: inputPath,
218
+ recursive: options.recursive
219
+ });
220
+ for (const filePath of result.updatedFiles) {
221
+ streams.stdout(`updated ${filePath}`);
222
+ }
223
+ });
224
+ program
225
+ .command("validate")
226
+ .argument("[path]", "Project directory or Spawnfile path", process.cwd())
227
+ .action(async (inputPath) => {
228
+ const plan = await handlers.buildCompilePlan(inputPath);
229
+ streams.stdout("validation succeeded");
230
+ streams.stdout(formatPlanSummary(plan));
231
+ });
232
+ program
233
+ .command("runtimes")
234
+ .description("List bundled runtime adapters")
235
+ .action(() => {
236
+ for (const runtimeName of handlers.listRuntimeAdapters()) {
237
+ streams.stdout(runtimeName);
238
+ }
239
+ });
240
+ const authCommand = program.command("auth").description("Manage local Spawnfile auth profiles");
241
+ const authImportCommand = authCommand
242
+ .command("import")
243
+ .description("Import auth material into a local auth profile");
244
+ authImportCommand
245
+ .command("env")
246
+ .argument("<file>", "Path to an env file")
247
+ .option("-p, --profile <name>", "Auth profile name", "default")
248
+ .action(async (filePath, options) => {
249
+ const profile = await handlers.importEnvFile(options.profile, filePath);
250
+ for (const line of formatAuthProfileSummary(profile)) {
251
+ streams.stdout(line);
252
+ }
253
+ });
254
+ authImportCommand
255
+ .command("claude-code")
256
+ .option("-p, --profile <name>", "Auth profile name", "default")
257
+ .option("--from <directory>", "Source Claude Code config directory")
258
+ .action(async (options) => {
259
+ const profile = await handlers.importClaudeCodeAuth(options.profile, options.from);
260
+ for (const line of formatAuthProfileSummary(profile)) {
261
+ streams.stdout(line);
262
+ }
263
+ });
264
+ authImportCommand
265
+ .command("codex")
266
+ .option("-p, --profile <name>", "Auth profile name", "default")
267
+ .option("--from <directory>", "Source Codex config directory")
268
+ .action(async (options) => {
269
+ const profile = await handlers.importCodexAuth(options.profile, options.from);
270
+ for (const line of formatAuthProfileSummary(profile)) {
271
+ streams.stdout(line);
272
+ }
273
+ });
274
+ authCommand
275
+ .command("sync")
276
+ .argument("[path]", "Project directory or Spawnfile path", process.cwd())
277
+ .option("-p, --profile <name>", "Auth profile name", "default")
278
+ .option("--env-file <file>", "Path to an env file with model API keys")
279
+ .option("--claude-from <directory>", "Source Claude Code config directory")
280
+ .option("--codex-from <directory>", "Source Codex config directory")
281
+ .action(async (inputPath, options) => {
282
+ const profile = await handlers.syncProjectAuth(inputPath, {
283
+ claudeCodeDirectory: options.claudeFrom,
284
+ codexDirectory: options.codexFrom,
285
+ envFilePath: options.envFile,
286
+ profileName: options.profile
287
+ });
288
+ for (const line of formatAuthProfileSummary(profile)) {
289
+ streams.stdout(line);
290
+ }
291
+ });
292
+ authCommand
293
+ .command("show")
294
+ .option("-p, --profile <name>", "Auth profile name", "default")
295
+ .action(async (options) => {
296
+ const profile = await handlers.requireAuthProfile(options.profile);
297
+ for (const line of formatAuthProfileSummary(profile)) {
298
+ streams.stdout(line);
299
+ }
300
+ });
301
+ try {
302
+ await program.parseAsync(argv, { from: "user" });
303
+ return 0;
304
+ }
305
+ catch (error) {
306
+ const message = isSpawnfileError(error)
307
+ ? `${error.code}: ${error.message}`
308
+ : error instanceof Error
309
+ ? error.message
310
+ : String(error);
311
+ streams.stderr(message);
312
+ return 1;
313
+ }
314
+ };
@@ -0,0 +1,21 @@
1
+ export interface AddAgentProjectOptions {
2
+ id: string;
3
+ path?: string;
4
+ runtime?: string;
5
+ }
6
+ export interface AddSubagentProjectOptions {
7
+ id: string;
8
+ path?: string;
9
+ }
10
+ export interface AddTeamProjectOptions {
11
+ id: string;
12
+ path?: string;
13
+ }
14
+ export interface AddProjectNodeResult {
15
+ createdFiles: string[];
16
+ targetDirectory: string;
17
+ updatedFiles: string[];
18
+ }
19
+ export declare const addAgentProject: (options: AddAgentProjectOptions) => Promise<AddProjectNodeResult>;
20
+ export declare const addSubagentProject: (options: AddSubagentProjectOptions) => Promise<AddProjectNodeResult>;
21
+ export declare const addTeamProject: (options: AddTeamProjectOptions) => Promise<AddProjectNodeResult>;