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.
- package/LICENSE +21 -0
- package/README.md +464 -0
- package/dist/.env.example +5 -0
- package/dist/Dockerfile +21 -0
- package/dist/auth/importers.d.ts +8 -0
- package/dist/auth/importers.js +93 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.js +5 -0
- package/dist/auth/paths.d.ts +6 -0
- package/dist/auth/paths.js +18 -0
- package/dist/auth/profileStore.d.ts +10 -0
- package/dist/auth/profileStore.js +125 -0
- package/dist/auth/runtimeCredentials.d.ts +14 -0
- package/dist/auth/runtimeCredentials.js +76 -0
- package/dist/auth/types.d.ts +22 -0
- package/dist/auth/types.js +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +4 -0
- package/dist/cli/runCli.d.ts +27 -0
- package/dist/cli/runCli.js +314 -0
- package/dist/compiler/addProjectNode.d.ts +21 -0
- package/dist/compiler/addProjectNode.js +126 -0
- package/dist/compiler/agentSurfaces.d.ts +4 -0
- package/dist/compiler/agentSurfaces.js +70 -0
- package/dist/compiler/buildCompilePlan.d.ts +2 -0
- package/dist/compiler/buildCompilePlan.js +258 -0
- package/dist/compiler/buildProject.d.ts +20 -0
- package/dist/compiler/buildProject.js +52 -0
- package/dist/compiler/compilePlanHelpers.d.ts +7 -0
- package/dist/compiler/compilePlanHelpers.js +39 -0
- package/dist/compiler/compileProject.d.ts +11 -0
- package/dist/compiler/compileProject.js +182 -0
- package/dist/compiler/containerArtifacts.d.ts +4 -0
- package/dist/compiler/containerArtifacts.js +64 -0
- package/dist/compiler/containerArtifactsPlans.d.ts +4 -0
- package/dist/compiler/containerArtifactsPlans.js +154 -0
- package/dist/compiler/containerArtifactsRender.d.ts +6 -0
- package/dist/compiler/containerArtifactsRender.js +237 -0
- package/dist/compiler/containerArtifactsTypes.d.ts +42 -0
- package/dist/compiler/containerArtifactsTypes.js +1 -0
- package/dist/compiler/discordSurface.d.ts +4 -0
- package/dist/compiler/discordSurface.js +28 -0
- package/dist/compiler/executionDefaults.d.ts +2 -0
- package/dist/compiler/executionDefaults.js +9 -0
- package/dist/compiler/helpers.d.ts +7 -0
- package/dist/compiler/helpers.js +35 -0
- package/dist/compiler/index.d.ts +9 -0
- package/dist/compiler/index.js +9 -0
- package/dist/compiler/initProject.d.ts +9 -0
- package/dist/compiler/initProject.js +46 -0
- package/dist/compiler/modelAuth.d.ts +2 -0
- package/dist/compiler/modelAuth.js +17 -0
- package/dist/compiler/modelEnv.d.ts +10 -0
- package/dist/compiler/modelEnv.js +97 -0
- package/dist/compiler/runProject.d.ts +34 -0
- package/dist/compiler/runProject.js +197 -0
- package/dist/compiler/runProjectAuth.d.ts +9 -0
- package/dist/compiler/runProjectAuth.js +59 -0
- package/dist/compiler/surfaceSupport.d.ts +2 -0
- package/dist/compiler/surfaceSupport.js +13 -0
- package/dist/compiler/surfaces.d.ts +21 -0
- package/dist/compiler/surfaces.js +59 -0
- package/dist/compiler/syncProjectAuth.d.ts +7 -0
- package/dist/compiler/syncProjectAuth.js +65 -0
- package/dist/compiler/types.d.ts +134 -0
- package/dist/compiler/types.js +1 -0
- package/dist/compiler/updateProjectModels.d.ts +20 -0
- package/dist/compiler/updateProjectModels.js +181 -0
- package/dist/container/rootfs/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/config.json +16 -0
- package/dist/container/rootfs/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/workspace/AGENTS.md +1 -0
- package/dist/e2e/cli.d.ts +1 -0
- package/dist/e2e/cli.js +40 -0
- package/dist/e2e/dockerAuth.d.ts +18 -0
- package/dist/e2e/dockerAuth.js +212 -0
- package/dist/e2e/fixtures.d.ts +2 -0
- package/dist/e2e/fixtures.js +49 -0
- package/dist/e2e/index.d.ts +4 -0
- package/dist/e2e/index.js +4 -0
- package/dist/e2e/runtimePrompts.d.ts +13 -0
- package/dist/e2e/runtimePrompts.js +132 -0
- package/dist/e2e/scenarios.d.ts +3 -0
- package/dist/e2e/scenarios.js +84 -0
- package/dist/e2e/types.d.ts +35 -0
- package/dist/e2e/types.js +1 -0
- package/dist/entrypoint.sh +71 -0
- package/dist/filesystem/index.d.ts +2 -0
- package/dist/filesystem/index.js +2 -0
- package/dist/filesystem/io.d.ts +11 -0
- package/dist/filesystem/io.js +57 -0
- package/dist/filesystem/paths.d.ts +6 -0
- package/dist/filesystem/paths.js +30 -0
- package/dist/manifest/index.d.ts +5 -0
- package/dist/manifest/index.js +5 -0
- package/dist/manifest/loadManifest.d.ts +12 -0
- package/dist/manifest/loadManifest.js +208 -0
- package/dist/manifest/renderSpawnfile.d.ts +2 -0
- package/dist/manifest/renderSpawnfile.js +211 -0
- package/dist/manifest/scaffold.d.ts +16 -0
- package/dist/manifest/scaffold.js +41 -0
- package/dist/manifest/schemas.d.ts +989 -0
- package/dist/manifest/schemas.js +314 -0
- package/dist/manifest/skillFrontmatter.d.ts +5 -0
- package/dist/manifest/skillFrontmatter.js +32 -0
- package/dist/manifest/surfaceSchemas.d.ts +148 -0
- package/dist/manifest/surfaceSchemas.js +162 -0
- package/dist/report/createDiagnostic.d.ts +2 -0
- package/dist/report/createDiagnostic.js +4 -0
- package/dist/report/createReport.d.ts +2 -0
- package/dist/report/createReport.js +7 -0
- package/dist/report/index.d.ts +4 -0
- package/dist/report/index.js +4 -0
- package/dist/report/types.d.ts +50 -0
- package/dist/report/types.js +1 -0
- package/dist/report/writeReport.d.ts +2 -0
- package/dist/report/writeReport.js +9 -0
- package/dist/runtime/common.d.ts +13 -0
- package/dist/runtime/common.js +63 -0
- package/dist/runtime/container.d.ts +8 -0
- package/dist/runtime/container.js +67 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/install.d.ts +51 -0
- package/dist/runtime/install.js +167 -0
- package/dist/runtime/openclaw/adapter.d.ts +2 -0
- package/dist/runtime/openclaw/adapter.js +194 -0
- package/dist/runtime/openclaw/runAuth.d.ts +2 -0
- package/dist/runtime/openclaw/runAuth.js +125 -0
- package/dist/runtime/openclaw/scaffold-assets/AGENTS.md +120 -0
- package/dist/runtime/openclaw/scaffold-assets/CLAUDE.md +5 -0
- package/dist/runtime/openclaw/scaffold-assets/IDENTITY.md +23 -0
- package/dist/runtime/openclaw/scaffold-assets/SOUL.md +36 -0
- package/dist/runtime/openclaw/scaffold.d.ts +2 -0
- package/dist/runtime/openclaw/scaffold.js +28 -0
- package/dist/runtime/openclaw/surfaces.d.ts +5 -0
- package/dist/runtime/openclaw/surfaces.js +253 -0
- package/dist/runtime/picoclaw/adapter.d.ts +2 -0
- package/dist/runtime/picoclaw/adapter.js +204 -0
- package/dist/runtime/picoclaw/runAuth.d.ts +2 -0
- package/dist/runtime/picoclaw/runAuth.js +117 -0
- package/dist/runtime/picoclaw/scaffold-assets/AGENTS.md +3 -0
- package/dist/runtime/picoclaw/scaffold-assets/CLAUDE.md +5 -0
- package/dist/runtime/picoclaw/scaffold-assets/IDENTITY.md +3 -0
- package/dist/runtime/picoclaw/scaffold-assets/SOUL.md +3 -0
- package/dist/runtime/picoclaw/scaffold.d.ts +2 -0
- package/dist/runtime/picoclaw/scaffold.js +28 -0
- package/dist/runtime/picoclaw/surfaces.d.ts +5 -0
- package/dist/runtime/picoclaw/surfaces.js +111 -0
- package/dist/runtime/registry.d.ts +41 -0
- package/dist/runtime/registry.js +134 -0
- package/dist/runtime/scaffoldAssets.d.ts +1 -0
- package/dist/runtime/scaffoldAssets.js +2 -0
- package/dist/runtime/tinyclaw/adapter.d.ts +2 -0
- package/dist/runtime/tinyclaw/adapter.js +263 -0
- package/dist/runtime/tinyclaw/runAuth.d.ts +2 -0
- package/dist/runtime/tinyclaw/runAuth.js +22 -0
- package/dist/runtime/tinyclaw/scaffold-assets/AGENTS.md +160 -0
- package/dist/runtime/tinyclaw/scaffold-assets/CLAUDE.md +5 -0
- package/dist/runtime/tinyclaw/scaffold-assets/SOUL.md +177 -0
- package/dist/runtime/tinyclaw/scaffold.d.ts +2 -0
- package/dist/runtime/tinyclaw/scaffold.js +24 -0
- package/dist/runtime/tinyclaw/surfaces.d.ts +8 -0
- package/dist/runtime/tinyclaw/surfaces.js +86 -0
- package/dist/runtime/types.d.ts +87 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtimes/picoclaw/agents/assistant/config.json +16 -0
- package/dist/runtimes/picoclaw/agents/assistant/workspace/AGENTS.md +1 -0
- package/dist/shared/constants.d.ts +7 -0
- package/dist/shared/constants.js +7 -0
- package/dist/shared/errors.d.ts +6 -0
- package/dist/shared/errors.js +9 -0
- package/dist/shared/index.d.ts +3 -0
- package/dist/shared/index.js +3 -0
- package/dist/shared/types.d.ts +9 -0
- package/dist/shared/types.js +1 -0
- package/dist/spawnfile-report.json +71 -0
- package/package.json +41 -0
- 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,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>;
|