spawnfile 0.1.11 → 0.1.13
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/README.md +21 -3
- package/dist/auth/runtimeCredentials.js +18 -2
- package/dist/cli/devCommands.d.ts +3 -0
- package/dist/cli/devCommands.js +115 -0
- package/dist/cli/lifecycleCommands.js +103 -1
- package/dist/cli/modelCommands.js +3 -0
- package/dist/cli/resolveCommandInput.d.ts +19 -0
- package/dist/cli/resolveCommandInput.js +25 -0
- package/dist/cli/runCli.d.ts +10 -1
- package/dist/cli/runCli.js +41 -13
- package/dist/cli/runtimeCommands.js +1 -0
- package/dist/cli/statusCommand.d.ts +43 -0
- package/dist/cli/statusCommand.js +428 -0
- package/dist/cli/surfaceCommands.js +4 -0
- package/dist/compiler/agentSurfaces.js +2 -4
- package/dist/compiler/buildProject.d.ts +9 -1
- package/dist/compiler/buildProject.js +44 -5
- package/dist/compiler/compileProject.d.ts +2 -0
- package/dist/compiler/compileProject.js +22 -11
- package/dist/compiler/containerArtifacts.d.ts +1 -0
- package/dist/compiler/containerArtifacts.js +208 -32
- package/dist/compiler/containerArtifactsPlans.js +7 -4
- package/dist/compiler/containerArtifactsRender.d.ts +7 -1
- package/dist/compiler/containerArtifactsRender.js +31 -69
- package/dist/compiler/containerArtifactsTypes.d.ts +9 -0
- package/dist/compiler/containerEntrypointRender.js +22 -32
- package/dist/compiler/containerPackageRender.d.ts +15 -0
- package/dist/compiler/containerPackageRender.js +63 -0
- package/dist/compiler/containerTargetResources.js +3 -1
- package/dist/compiler/index.d.ts +1 -0
- package/dist/compiler/index.js +1 -0
- package/dist/compiler/moltnetArtifacts.js +21 -1
- package/dist/compiler/moltnetBinaries.d.ts +4 -1
- package/dist/compiler/moltnetBinaries.js +24 -3
- package/dist/compiler/moltnetClientConfig.d.ts +1 -1
- package/dist/compiler/moltnetClientConfig.js +14 -16
- package/dist/compiler/moltnetNodeConfig.js +2 -4
- package/dist/compiler/moltnetResolution.js +1 -1
- package/dist/compiler/moltnetRoomMemberships.js +1 -2
- package/dist/compiler/moltnetRuntimeConfig.d.ts +1 -1
- package/dist/compiler/moltnetRuntimeConfig.js +9 -10
- package/dist/compiler/networkBinding.d.ts +14 -0
- package/dist/compiler/networkBinding.js +34 -0
- package/dist/compiler/publishProject.d.ts +13 -0
- package/dist/compiler/publishProject.js +72 -0
- package/dist/compiler/runProject.d.ts +26 -12
- package/dist/compiler/runProject.js +109 -126
- package/dist/compiler/runProjectAuth.d.ts +5 -0
- package/dist/compiler/runProjectAuth.js +98 -0
- package/dist/compiler/runProjectDocker.d.ts +19 -0
- package/dist/compiler/runProjectDocker.js +202 -0
- package/dist/compiler/types.d.ts +1 -2
- package/dist/compiler/upProject.d.ts +6 -0
- package/dist/compiler/upProject.js +43 -8
- package/dist/compiler/view/buildOrganizationView.js +4 -1
- package/dist/compiler/view/declarations.d.ts +4 -0
- package/dist/compiler/view/declarations.js +104 -0
- package/dist/compiler/view/renderNetworks.js +1 -2
- package/dist/compiler/view/renderTree.js +6 -2
- package/dist/compiler/view/types.d.ts +64 -1
- package/dist/deployment/dockerInspect.d.ts +29 -0
- package/dist/deployment/dockerInspect.js +130 -0
- package/dist/deployment/dockerLabels.d.ts +16 -0
- package/dist/deployment/dockerLabels.js +32 -0
- package/dist/deployment/dockerLogs.d.ts +24 -0
- package/dist/deployment/dockerLogs.js +96 -0
- package/dist/deployment/dockerManager.d.ts +59 -0
- package/dist/deployment/dockerManager.js +76 -0
- package/dist/deployment/dockerProbeGateway.d.ts +16 -0
- package/dist/deployment/dockerProbeGateway.js +48 -0
- package/dist/deployment/dockerRecover.d.ts +17 -0
- package/dist/deployment/dockerRecover.js +129 -0
- package/dist/deployment/homeStore.d.ts +19 -0
- package/dist/deployment/homeStore.js +130 -0
- package/dist/deployment/index.d.ts +10 -0
- package/dist/deployment/index.js +10 -0
- package/dist/deployment/names.d.ts +3 -0
- package/dist/deployment/names.js +12 -0
- package/dist/deployment/record.d.ts +76 -0
- package/dist/deployment/record.js +174 -0
- package/dist/deployment/target.d.ts +35 -0
- package/dist/deployment/target.js +96 -0
- package/dist/dev/activity.d.ts +14 -0
- package/dist/dev/activity.js +48 -0
- package/dist/dev/docker.d.ts +16 -0
- package/dist/dev/docker.js +71 -0
- package/dist/dev/index.d.ts +2 -0
- package/dist/dev/index.js +2 -0
- package/dist/dev/project.d.ts +35 -0
- package/dist/dev/project.js +164 -0
- package/dist/distribution/buildDistributionReport.d.ts +22 -0
- package/dist/distribution/buildDistributionReport.js +51 -0
- package/dist/distribution/consumeImage.d.ts +29 -0
- package/dist/distribution/consumeImage.js +254 -0
- package/dist/distribution/consumeImageSupport.d.ts +18 -0
- package/dist/distribution/consumeImageSupport.js +63 -0
- package/dist/distribution/distributionReportSchema.d.ts +84 -0
- package/dist/distribution/distributionReportSchema.js +107 -0
- package/dist/distribution/dockerRunner.d.ts +5 -0
- package/dist/distribution/dockerRunner.js +21 -0
- package/dist/distribution/extractImage.d.ts +25 -0
- package/dist/distribution/extractImage.js +96 -0
- package/dist/distribution/fingerprint.d.ts +2 -0
- package/dist/distribution/fingerprint.js +14 -0
- package/dist/distribution/imageRef.d.ts +9 -0
- package/dist/distribution/imageRef.js +65 -0
- package/dist/distribution/imageRuntimeAuth.d.ts +19 -0
- package/dist/distribution/imageRuntimeAuth.js +71 -0
- package/dist/distribution/index.d.ts +24 -0
- package/dist/distribution/index.js +16 -0
- package/dist/distribution/preflight.d.ts +11 -0
- package/dist/distribution/preflight.js +65 -0
- package/dist/distribution/projectImageView.d.ts +8 -0
- package/dist/distribution/projectImageView.js +83 -0
- package/dist/distribution/projectName.d.ts +1 -0
- package/dist/distribution/projectName.js +12 -0
- package/dist/distribution/renderImageInterface.d.ts +30 -0
- package/dist/distribution/renderImageInterface.js +86 -0
- package/dist/distribution/tarReader.d.ts +10 -0
- package/dist/distribution/tarReader.js +69 -0
- package/dist/distribution/types.d.ts +80 -0
- package/dist/distribution/types.js +4 -0
- package/dist/distribution/verifyDistributionReport.d.ts +12 -0
- package/dist/distribution/verifyDistributionReport.js +29 -0
- package/dist/filesystem/paths.d.ts +6 -0
- package/dist/filesystem/paths.js +11 -0
- package/dist/manifest/loadManifest.js +22 -2
- package/dist/manifest/renderSpawnfile.js +2 -4
- package/dist/manifest/schemas.d.ts +17 -41
- package/dist/manifest/surfaceSchemas.d.ts +19 -47
- package/dist/manifest/surfaceSchemas.js +3 -6
- package/dist/report/createReport.d.ts +7 -1
- package/dist/report/createReport.js +28 -1
- package/dist/report/types.d.ts +43 -0
- package/dist/runtime/common.d.ts +2 -0
- package/dist/runtime/common.js +1 -1
- package/dist/runtime/container.d.ts +1 -0
- package/dist/runtime/container.js +82 -15
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/install.d.ts +1 -11
- package/dist/runtime/install.js +27 -30
- package/dist/runtime/openclaw/adapter.js +9 -1
- package/dist/runtime/openclaw/configAuth.d.ts +15 -0
- package/dist/runtime/openclaw/configAuth.js +46 -0
- package/dist/runtime/openclaw/runAuth.d.ts +6 -0
- package/dist/runtime/openclaw/runAuth.js +7 -55
- package/dist/runtime/openclaw/statusProbes.d.ts +2 -0
- package/dist/runtime/openclaw/statusProbes.js +31 -0
- package/dist/runtime/pi/adapter.d.ts +4 -0
- package/dist/runtime/pi/adapter.js +198 -0
- package/dist/runtime/pi/appActivitySource.d.ts +1 -0
- package/dist/runtime/pi/appActivitySource.js +87 -0
- package/dist/runtime/pi/appCoreSource.d.ts +1 -0
- package/dist/runtime/pi/appCoreSource.js +354 -0
- package/dist/runtime/pi/appPreludeSource.d.ts +1 -0
- package/dist/runtime/pi/appPreludeSource.js +103 -0
- package/dist/runtime/pi/appSource.d.ts +1 -0
- package/dist/runtime/pi/appSource.js +8 -0
- package/dist/runtime/pi/appTemplate.d.ts +28 -0
- package/dist/runtime/pi/appTemplate.js +144 -0
- package/dist/runtime/pi/runAuth.d.ts +2 -0
- package/dist/runtime/pi/runAuth.js +68 -0
- package/dist/runtime/picoclaw/adapter.js +10 -0
- package/dist/runtime/picoclaw/runAuth.d.ts +7 -0
- package/dist/runtime/picoclaw/runAuth.js +8 -68
- package/dist/runtime/picoclaw/statusProbes.d.ts +2 -0
- package/dist/runtime/picoclaw/statusProbes.js +100 -0
- package/dist/runtime/registry.d.ts +0 -5
- package/dist/runtime/registry.js +5 -12
- package/dist/runtime/statusProbes.d.ts +16 -0
- package/dist/runtime/statusProbes.js +41 -0
- package/dist/runtime/types.d.ts +53 -1
- package/dist/shared/errors.d.ts +6 -0
- package/dist/shared/errors.js +9 -0
- package/dist/status/buildStatus.d.ts +13 -0
- package/dist/status/buildStatus.js +266 -0
- package/dist/status/compileReport.d.ts +96 -0
- package/dist/status/compileReport.js +230 -0
- package/dist/status/compiledContainerObservations.d.ts +5 -0
- package/dist/status/compiledContainerObservations.js +63 -0
- package/dist/status/deploymentLogs.d.ts +12 -0
- package/dist/status/deploymentLogs.js +45 -0
- package/dist/status/deployments.d.ts +14 -0
- package/dist/status/deployments.js +121 -0
- package/dist/status/imageStatusReport.d.ts +10 -0
- package/dist/status/imageStatusReport.js +73 -0
- package/dist/status/index.d.ts +14 -0
- package/dist/status/index.js +14 -0
- package/dist/status/moltnetProbes.d.ts +21 -0
- package/dist/status/moltnetProbes.js +240 -0
- package/dist/status/registryDrift.d.ts +28 -0
- package/dist/status/registryDrift.js +119 -0
- package/dist/status/renderStatus.d.ts +2 -0
- package/dist/status/renderStatus.js +230 -0
- package/dist/status/runtimeProbes.d.ts +13 -0
- package/dist/status/runtimeProbes.js +111 -0
- package/dist/status/selectionSubjects.d.ts +8 -0
- package/dist/status/selectionSubjects.js +77 -0
- package/dist/status/selectors.d.ts +14 -0
- package/dist/status/selectors.js +90 -0
- package/dist/status/traversal.d.ts +6 -0
- package/dist/status/traversal.js +15 -0
- package/dist/status/types.d.ts +113 -0
- package/dist/status/types.js +1 -0
- package/package.json +9 -2
- package/runtimes.yaml +44 -21
- package/dist/runtime/tinyclaw/adapter.d.ts +0 -2
- package/dist/runtime/tinyclaw/adapter.js +0 -284
- package/dist/runtime/tinyclaw/runAuth.d.ts +0 -2
- package/dist/runtime/tinyclaw/runAuth.js +0 -49
- package/dist/runtime/tinyclaw/scaffold-assets/AGENTS.md +0 -160
- package/dist/runtime/tinyclaw/scaffold-assets/CLAUDE.md +0 -5
- package/dist/runtime/tinyclaw/scaffold-assets/SOUL.md +0 -177
- package/dist/runtime/tinyclaw/scaffold.d.ts +0 -2
- package/dist/runtime/tinyclaw/scaffold.js +0 -24
- package/dist/runtime/tinyclaw/schedules.d.ts +0 -9
- package/dist/runtime/tinyclaw/schedules.js +0 -62
- package/dist/runtime/tinyclaw/surfaces.d.ts +0 -8
- package/dist/runtime/tinyclaw/surfaces.js +0 -94
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
const GENERATED_RUNTIME_SECRET_NAMES = new Set([
|
|
3
|
+
"OPENCLAW_GATEWAY_TOKEN",
|
|
4
|
+
"OPENCLAW_HOOKS_TOKEN"
|
|
5
|
+
]);
|
|
6
|
+
/** Derives a kebab-case deployment name from the image repository component. */
|
|
7
|
+
export const deriveDeploymentName = (ref) => {
|
|
8
|
+
const base = ref.name.split("/").pop() ?? ref.name;
|
|
9
|
+
const normalized = base
|
|
10
|
+
.toLowerCase()
|
|
11
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
12
|
+
.replace(/^-+|-+$/g, "");
|
|
13
|
+
return normalized || "deployment";
|
|
14
|
+
};
|
|
15
|
+
/** Per-deployment volume name so two deployments never share a store. */
|
|
16
|
+
export const deriveVolumeName = (deploymentName, mountId) => {
|
|
17
|
+
const safeMount = mountId.replace(/[^A-Za-z0-9_.-]+/g, "-");
|
|
18
|
+
return `spawnfile_${deploymentName}_${safeMount}`;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Builds the container environment for a sourceless deployment: auth-profile
|
|
22
|
+
* and env-file values, process-env overrides for declared secrets, and
|
|
23
|
+
* generated runtime tokens that the user must never supply.
|
|
24
|
+
*/
|
|
25
|
+
export const resolveImageEnvironment = (input) => {
|
|
26
|
+
const env = {
|
|
27
|
+
...input.authValues,
|
|
28
|
+
...(input.envFileEnv ?? {})
|
|
29
|
+
};
|
|
30
|
+
const declaredSecrets = new Set();
|
|
31
|
+
for (const category of ["model", "project", "runtime", "surface"]) {
|
|
32
|
+
for (const entry of input.report.secrets[category]) {
|
|
33
|
+
declaredSecrets.add(entry.name);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const name of new Set([...Object.keys(env), ...declaredSecrets])) {
|
|
37
|
+
const processValue = process.env[name];
|
|
38
|
+
if (typeof processValue === "string" && processValue.length > 0) {
|
|
39
|
+
env[name] = processValue;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const category of ["runtime"]) {
|
|
43
|
+
for (const entry of input.report.secrets[category]) {
|
|
44
|
+
if (!entry.generated) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const hasValue = typeof env[entry.name] === "string" && env[entry.name].length > 0;
|
|
48
|
+
if (!hasValue && GENERATED_RUNTIME_SECRET_NAMES.has(entry.name)) {
|
|
49
|
+
env[entry.name] = randomBytes(24).toString("hex");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
for (const [name, value] of Object.entries(env)) {
|
|
54
|
+
if (value.includes("\n")) {
|
|
55
|
+
delete env[name];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return env;
|
|
59
|
+
};
|
|
60
|
+
export const renderEnvFileContent = (env) => Object.entries(env)
|
|
61
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
62
|
+
.map(([name, value]) => `${name}=${value}`)
|
|
63
|
+
.join("\n") + "\n";
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { DistributionReport } from "./types.js";
|
|
3
|
+
export declare const distributionReportSchema: z.ZodObject<{
|
|
4
|
+
compile_fingerprint: z.ZodString;
|
|
5
|
+
generated_at: z.ZodString;
|
|
6
|
+
internal_ports: z.ZodArray<z.ZodNumber>;
|
|
7
|
+
model_auth_methods: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
8
|
+
moltnet: z.ZodObject<{
|
|
9
|
+
networks: z.ZodArray<z.ZodObject<{
|
|
10
|
+
binding: z.ZodOptional<z.ZodLiteral<"env">>;
|
|
11
|
+
id: z.ZodString;
|
|
12
|
+
server_mode: z.ZodUnion<readonly [z.ZodLiteral<"external">, z.ZodLiteral<"managed">]>;
|
|
13
|
+
}, z.core.$strict>>;
|
|
14
|
+
}, z.core.$strict>;
|
|
15
|
+
organization: z.ZodObject<{
|
|
16
|
+
agents: z.ZodArray<z.ZodObject<{
|
|
17
|
+
id: z.ZodString;
|
|
18
|
+
name: z.ZodString;
|
|
19
|
+
runtime: z.ZodNullable<z.ZodString>;
|
|
20
|
+
teams: z.ZodArray<z.ZodString>;
|
|
21
|
+
}, z.core.$strict>>;
|
|
22
|
+
project: z.ZodString;
|
|
23
|
+
teams: z.ZodArray<z.ZodObject<{
|
|
24
|
+
agents: z.ZodArray<z.ZodString>;
|
|
25
|
+
id: z.ZodString;
|
|
26
|
+
name: z.ZodString;
|
|
27
|
+
}, z.core.$strict>>;
|
|
28
|
+
}, z.core.$strict>;
|
|
29
|
+
persistent_mounts: z.ZodArray<z.ZodObject<{
|
|
30
|
+
durability: z.ZodLiteral<"persistent">;
|
|
31
|
+
id: z.ZodString;
|
|
32
|
+
kind: z.ZodLiteral<"volume">;
|
|
33
|
+
target: z.ZodString;
|
|
34
|
+
}, z.core.$strict>>;
|
|
35
|
+
port_mappings: z.ZodArray<z.ZodObject<{
|
|
36
|
+
internal_port: z.ZodNumber;
|
|
37
|
+
published_port: z.ZodNumber;
|
|
38
|
+
}, z.core.$strict>>;
|
|
39
|
+
ports: z.ZodArray<z.ZodNumber>;
|
|
40
|
+
resources: z.ZodArray<z.ZodObject<{
|
|
41
|
+
id: z.ZodString;
|
|
42
|
+
kind: z.ZodUnion<readonly [z.ZodLiteral<"git">, z.ZodLiteral<"volume">]>;
|
|
43
|
+
link_path: z.ZodString;
|
|
44
|
+
mode: z.ZodUnion<readonly [z.ZodLiteral<"mutable">, z.ZodLiteral<"readonly">]>;
|
|
45
|
+
mount: z.ZodString;
|
|
46
|
+
sharing: z.ZodUnion<readonly [z.ZodLiteral<"per_agent">, z.ZodLiteral<"team">]>;
|
|
47
|
+
}, z.core.$strict>>;
|
|
48
|
+
runtime_instances: z.ZodArray<z.ZodObject<{
|
|
49
|
+
config_path: z.ZodString;
|
|
50
|
+
home_path: z.ZodNullable<z.ZodString>;
|
|
51
|
+
id: z.ZodString;
|
|
52
|
+
internal_port: z.ZodNullable<z.ZodNumber>;
|
|
53
|
+
model_auth_methods: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
54
|
+
model_secrets_required: z.ZodArray<z.ZodString>;
|
|
55
|
+
node_ids: z.ZodArray<z.ZodString>;
|
|
56
|
+
published_port: z.ZodNullable<z.ZodNumber>;
|
|
57
|
+
runtime: z.ZodString;
|
|
58
|
+
workspace_path: z.ZodString;
|
|
59
|
+
}, z.core.$strict>>;
|
|
60
|
+
secrets: z.ZodObject<{
|
|
61
|
+
model: z.ZodArray<z.ZodObject<{
|
|
62
|
+
generated: z.ZodBoolean;
|
|
63
|
+
name: z.ZodString;
|
|
64
|
+
required: z.ZodBoolean;
|
|
65
|
+
}, z.core.$strict>>;
|
|
66
|
+
project: z.ZodArray<z.ZodObject<{
|
|
67
|
+
generated: z.ZodBoolean;
|
|
68
|
+
name: z.ZodString;
|
|
69
|
+
required: z.ZodBoolean;
|
|
70
|
+
}, z.core.$strict>>;
|
|
71
|
+
runtime: z.ZodArray<z.ZodObject<{
|
|
72
|
+
generated: z.ZodBoolean;
|
|
73
|
+
name: z.ZodString;
|
|
74
|
+
required: z.ZodBoolean;
|
|
75
|
+
}, z.core.$strict>>;
|
|
76
|
+
surface: z.ZodArray<z.ZodObject<{
|
|
77
|
+
generated: z.ZodBoolean;
|
|
78
|
+
name: z.ZodString;
|
|
79
|
+
required: z.ZodBoolean;
|
|
80
|
+
}, z.core.$strict>>;
|
|
81
|
+
}, z.core.$strict>;
|
|
82
|
+
version: z.ZodLiteral<"spawnfile.distribution-report.v1">;
|
|
83
|
+
}, z.core.$strict>;
|
|
84
|
+
export declare const parseDistributionReport: (source: unknown, sourceLabel?: string) => DistributionReport;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
3
|
+
import { DISTRIBUTION_REPORT_VERSION } from "./types.js";
|
|
4
|
+
const secretEntrySchema = z.object({
|
|
5
|
+
generated: z.boolean(),
|
|
6
|
+
name: z.string().min(1),
|
|
7
|
+
required: z.boolean()
|
|
8
|
+
}).strict();
|
|
9
|
+
// Path fields from an untrusted image flow into `docker run -v` mount specs. A
|
|
10
|
+
// crafted value containing `:` would inject extra mount fields (e.g. drop `:ro`
|
|
11
|
+
// or add options), and `..` could redirect the mount target. Constrain every
|
|
12
|
+
// path the consumer interpolates to a clean absolute POSIX path so a hostile
|
|
13
|
+
// report is rejected up front; a legitimately compiled image always satisfies this.
|
|
14
|
+
const containerPathSchema = z
|
|
15
|
+
.string()
|
|
16
|
+
.min(1)
|
|
17
|
+
.refine((value) => value.startsWith("/") &&
|
|
18
|
+
!value.includes(":") &&
|
|
19
|
+
!/\s/.test(value) &&
|
|
20
|
+
!value.split("/").includes(".."), { message: "must be a clean absolute POSIX path (no ':', whitespace, or '..' segment)" });
|
|
21
|
+
// Ports are interpolated into `-p host:container`; bound them to the valid TCP range.
|
|
22
|
+
const portSchema = z.number().int().min(1).max(65535);
|
|
23
|
+
// A runtime instance id is interpolated into a host path (the auth-profiles temp
|
|
24
|
+
// file) and then into a `docker run -v` source by the runtime adapters, so an
|
|
25
|
+
// unconstrained value could traverse with `..` or inject mount fields with `:`.
|
|
26
|
+
// Legitimate ids are compile-generated `<kind>-<slug>` identifiers.
|
|
27
|
+
const instanceIdSchema = z
|
|
28
|
+
.string()
|
|
29
|
+
.min(1)
|
|
30
|
+
.refine((value) => /^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(value) && !value.includes(".."), {
|
|
31
|
+
message: "must be an identifier (letters, digits, '_', '.', '-'; no '/', ':', whitespace, or '..')"
|
|
32
|
+
});
|
|
33
|
+
const runtimeInstanceSchema = z.object({
|
|
34
|
+
config_path: containerPathSchema,
|
|
35
|
+
home_path: containerPathSchema.nullable(),
|
|
36
|
+
id: instanceIdSchema,
|
|
37
|
+
internal_port: portSchema.nullable(),
|
|
38
|
+
model_auth_methods: z.record(z.string(), z.string()),
|
|
39
|
+
model_secrets_required: z.array(z.string()),
|
|
40
|
+
node_ids: z.array(z.string()),
|
|
41
|
+
published_port: portSchema.nullable(),
|
|
42
|
+
runtime: z.string().min(1),
|
|
43
|
+
workspace_path: containerPathSchema
|
|
44
|
+
}).strict();
|
|
45
|
+
export const distributionReportSchema = z.object({
|
|
46
|
+
compile_fingerprint: z.string().min(1),
|
|
47
|
+
generated_at: z.string().min(1),
|
|
48
|
+
internal_ports: z.array(portSchema),
|
|
49
|
+
model_auth_methods: z.record(z.string(), z.string()),
|
|
50
|
+
moltnet: z.object({
|
|
51
|
+
networks: z.array(z.object({
|
|
52
|
+
binding: z.literal("env").optional(),
|
|
53
|
+
id: z.string().min(1),
|
|
54
|
+
server_mode: z.union([z.literal("external"), z.literal("managed")])
|
|
55
|
+
}).strict())
|
|
56
|
+
}).strict(),
|
|
57
|
+
organization: z.object({
|
|
58
|
+
agents: z.array(z.object({
|
|
59
|
+
id: z.string().min(1),
|
|
60
|
+
name: z.string(),
|
|
61
|
+
runtime: z.string().nullable(),
|
|
62
|
+
teams: z.array(z.string())
|
|
63
|
+
}).strict()),
|
|
64
|
+
project: z.string().min(1),
|
|
65
|
+
teams: z.array(z.object({
|
|
66
|
+
agents: z.array(z.string()),
|
|
67
|
+
id: z.string().min(1),
|
|
68
|
+
name: z.string()
|
|
69
|
+
}).strict())
|
|
70
|
+
}).strict(),
|
|
71
|
+
persistent_mounts: z.array(z.object({
|
|
72
|
+
durability: z.literal("persistent"),
|
|
73
|
+
// Sanitized by deriveVolumeName before use, but constrained here too so the
|
|
74
|
+
// schema alone keeps the volume-name source clean (defense in depth).
|
|
75
|
+
id: instanceIdSchema,
|
|
76
|
+
kind: z.literal("volume"),
|
|
77
|
+
target: containerPathSchema
|
|
78
|
+
}).strict()),
|
|
79
|
+
port_mappings: z.array(z.object({
|
|
80
|
+
internal_port: portSchema,
|
|
81
|
+
published_port: portSchema
|
|
82
|
+
}).strict()),
|
|
83
|
+
ports: z.array(portSchema),
|
|
84
|
+
resources: z.array(z.object({
|
|
85
|
+
id: z.string().min(1),
|
|
86
|
+
kind: z.union([z.literal("git"), z.literal("volume")]),
|
|
87
|
+
link_path: z.string(),
|
|
88
|
+
mode: z.union([z.literal("mutable"), z.literal("readonly")]),
|
|
89
|
+
mount: z.string(),
|
|
90
|
+
sharing: z.union([z.literal("per_agent"), z.literal("team")])
|
|
91
|
+
}).strict()),
|
|
92
|
+
runtime_instances: z.array(runtimeInstanceSchema),
|
|
93
|
+
secrets: z.object({
|
|
94
|
+
model: z.array(secretEntrySchema),
|
|
95
|
+
project: z.array(secretEntrySchema),
|
|
96
|
+
runtime: z.array(secretEntrySchema),
|
|
97
|
+
surface: z.array(secretEntrySchema)
|
|
98
|
+
}).strict(),
|
|
99
|
+
version: z.literal(DISTRIBUTION_REPORT_VERSION)
|
|
100
|
+
}).strict();
|
|
101
|
+
export const parseDistributionReport = (source, sourceLabel = "distribution report") => {
|
|
102
|
+
const parsed = distributionReportSchema.safeParse(source);
|
|
103
|
+
if (!parsed.success) {
|
|
104
|
+
throw new SpawnfileError("validation_error", `Invalid ${sourceLabel}: ${z.prettifyError(parsed.error)}`);
|
|
105
|
+
}
|
|
106
|
+
return parsed.data;
|
|
107
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export interface DockerCommandRunner {
|
|
2
|
+
/** Runs docker with args; resolves stdout as a Buffer. Rejects on non-zero exit. */
|
|
3
|
+
(args: string[]): Promise<Buffer>;
|
|
4
|
+
}
|
|
5
|
+
export declare const createConsumerDockerRunner: (dockerCommand: string, baseArgs: string[]) => DockerCommandRunner;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
3
|
+
export const createConsumerDockerRunner = (dockerCommand, baseArgs) => async (args) => new Promise((resolve, reject) => {
|
|
4
|
+
const finalArgs = [...baseArgs, ...args];
|
|
5
|
+
const child = spawn(dockerCommand, finalArgs, {
|
|
6
|
+
env: { ...process.env },
|
|
7
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
8
|
+
});
|
|
9
|
+
const stdout = [];
|
|
10
|
+
const stderr = [];
|
|
11
|
+
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
|
12
|
+
child.stderr.on("data", (chunk) => stderr.push(String(chunk)));
|
|
13
|
+
child.on("error", reject);
|
|
14
|
+
child.on("close", (code) => {
|
|
15
|
+
if (code === 0) {
|
|
16
|
+
resolve(Buffer.concat(stdout));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
reject(new SpawnfileError("runtime_error", `docker ${finalArgs[0]} failed (${code}): ${stderr.join("").trim()}`));
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { DockerCommandRunner } from "./dockerRunner.js";
|
|
2
|
+
import type { DistributionReport } from "./types.js";
|
|
3
|
+
export type { DockerCommandRunner } from "./dockerRunner.js";
|
|
4
|
+
export interface ImageInspection {
|
|
5
|
+
compileFingerprint: string;
|
|
6
|
+
labels: Record<string, string>;
|
|
7
|
+
report: DistributionReport;
|
|
8
|
+
}
|
|
9
|
+
export interface ExtractImageOptions {
|
|
10
|
+
dockerCommand?: string;
|
|
11
|
+
dockerContext?: string;
|
|
12
|
+
dockerHost?: string;
|
|
13
|
+
pull?: boolean;
|
|
14
|
+
runDocker?: DockerCommandRunner;
|
|
15
|
+
}
|
|
16
|
+
export declare const resolveDockerBaseArgs: (options: {
|
|
17
|
+
dockerContext?: string;
|
|
18
|
+
dockerHost?: string;
|
|
19
|
+
}) => string[];
|
|
20
|
+
/**
|
|
21
|
+
* Pulls (optionally), inspects labels, and extracts the embedded distribution
|
|
22
|
+
* report from an image without starting its entrypoint. Uses a stopped helper
|
|
23
|
+
* container and guarantees cleanup.
|
|
24
|
+
*/
|
|
25
|
+
export declare const extractImageReport: (imageRef: string, options?: ExtractImageOptions) => Promise<ImageInspection>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
2
|
+
import { createConsumerDockerRunner } from "./dockerRunner.js";
|
|
3
|
+
import { parseDistributionReport } from "./distributionReportSchema.js";
|
|
4
|
+
import { extractSingleFileFromTar } from "./tarReader.js";
|
|
5
|
+
import { DISTRIBUTION_REPORT_IMAGE_PATH, IMAGE_CONTRACT_VERSION } from "./types.js";
|
|
6
|
+
const REPORT_SIZE_CAP_BYTES = 4 * 1024 * 1024;
|
|
7
|
+
const LABEL_VALUE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/;
|
|
8
|
+
export const resolveDockerBaseArgs = (options) => {
|
|
9
|
+
if (options.dockerContext) {
|
|
10
|
+
return ["--context", options.dockerContext];
|
|
11
|
+
}
|
|
12
|
+
if (options.dockerHost) {
|
|
13
|
+
return ["--host", options.dockerHost];
|
|
14
|
+
}
|
|
15
|
+
return [];
|
|
16
|
+
};
|
|
17
|
+
const assertContractLabels = (labels) => {
|
|
18
|
+
const contract = labels["com.spawnfile.image_contract"];
|
|
19
|
+
if (!contract) {
|
|
20
|
+
throw new SpawnfileError("validation_error", "Image is not a Spawnfile image: missing com.spawnfile.image_contract label");
|
|
21
|
+
}
|
|
22
|
+
if (contract !== IMAGE_CONTRACT_VERSION) {
|
|
23
|
+
throw new SpawnfileError("validation_error", `Unsupported image contract ${contract}; this CLI supports ${IMAGE_CONTRACT_VERSION}`);
|
|
24
|
+
}
|
|
25
|
+
for (const key of ["com.spawnfile.project", "com.spawnfile.compile_fingerprint"]) {
|
|
26
|
+
const value = labels[key];
|
|
27
|
+
if (!value || !LABEL_VALUE_PATTERN.test(value)) {
|
|
28
|
+
throw new SpawnfileError("validation_error", `Image label ${key} is missing or not identifier-only`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Pulls (optionally), inspects labels, and extracts the embedded distribution
|
|
34
|
+
* report from an image without starting its entrypoint. Uses a stopped helper
|
|
35
|
+
* container and guarantees cleanup.
|
|
36
|
+
*/
|
|
37
|
+
export const extractImageReport = async (imageRef, options = {}) => {
|
|
38
|
+
const dockerCommand = options.dockerCommand ?? "docker";
|
|
39
|
+
const baseArgs = resolveDockerBaseArgs(options);
|
|
40
|
+
const runDocker = options.runDocker ?? createConsumerDockerRunner(dockerCommand, baseArgs);
|
|
41
|
+
if (options.pull) {
|
|
42
|
+
await runDocker(["pull", imageRef]);
|
|
43
|
+
}
|
|
44
|
+
let labelsRaw;
|
|
45
|
+
try {
|
|
46
|
+
labelsRaw = (await runDocker([
|
|
47
|
+
"image",
|
|
48
|
+
"inspect",
|
|
49
|
+
"--format",
|
|
50
|
+
"{{json .Config.Labels}}",
|
|
51
|
+
imageRef
|
|
52
|
+
])).toString("utf8").trim();
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
56
|
+
if (/No such image|not found/i.test(message)) {
|
|
57
|
+
throw new SpawnfileError("validation_error", `Image ${imageRef} is not available locally. Pull it first with --pull, or publish it with 'spawnfile publish'.`);
|
|
58
|
+
}
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
let labels;
|
|
62
|
+
try {
|
|
63
|
+
labels = JSON.parse(labelsRaw) ?? {};
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
throw new SpawnfileError("validation_error", `Unable to read labels for image ${imageRef}`);
|
|
67
|
+
}
|
|
68
|
+
assertContractLabels(labels);
|
|
69
|
+
const reportPath = labels["com.spawnfile.report"] ?? DISTRIBUTION_REPORT_IMAGE_PATH;
|
|
70
|
+
const helperName = `spawnfile-inspect-${Date.now().toString(36)}-${Math.floor(Number(process.pid)).toString(36)}`;
|
|
71
|
+
await runDocker(["create", "--name", helperName, imageRef]);
|
|
72
|
+
let tar;
|
|
73
|
+
try {
|
|
74
|
+
tar = await runDocker(["cp", `${helperName}:${reportPath}`, "-"]);
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
await runDocker(["rm", "-f", helperName]).catch(() => undefined);
|
|
78
|
+
}
|
|
79
|
+
const reportBytes = extractSingleFileFromTar(tar, { maxBytes: REPORT_SIZE_CAP_BYTES });
|
|
80
|
+
let parsedJson;
|
|
81
|
+
try {
|
|
82
|
+
parsedJson = JSON.parse(reportBytes.toString("utf8"));
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
throw new SpawnfileError("validation_error", `Embedded report in ${imageRef} is not valid JSON`);
|
|
86
|
+
}
|
|
87
|
+
const report = parseDistributionReport(parsedJson, `embedded report in ${imageRef}`);
|
|
88
|
+
if (report.compile_fingerprint !== labels["com.spawnfile.compile_fingerprint"]) {
|
|
89
|
+
throw new SpawnfileError("validation_error", `Embedded report fingerprint does not match the image label for ${imageRef}`);
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
compileFingerprint: report.compile_fingerprint,
|
|
93
|
+
labels,
|
|
94
|
+
report
|
|
95
|
+
};
|
|
96
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
const stableStringify = (value) => {
|
|
3
|
+
if (Array.isArray(value)) {
|
|
4
|
+
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
5
|
+
}
|
|
6
|
+
if (value && typeof value === "object") {
|
|
7
|
+
return `{${Object.entries(value)
|
|
8
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
9
|
+
.map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`)
|
|
10
|
+
.join(",")}}`;
|
|
11
|
+
}
|
|
12
|
+
return JSON.stringify(value);
|
|
13
|
+
};
|
|
14
|
+
export const createDistributionFingerprint = (report) => `sf1:${createHash("sha1").update(stableStringify(report)).digest("hex").slice(0, 12)}`;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ParsedImageReference {
|
|
2
|
+
digest: string | null;
|
|
3
|
+
name: string;
|
|
4
|
+
registry: string | null;
|
|
5
|
+
tag: string | null;
|
|
6
|
+
}
|
|
7
|
+
export declare const parseImageReference: (value: string) => ParsedImageReference | null;
|
|
8
|
+
export declare const parseImplicitImageReference: (value: string) => ParsedImageReference | null;
|
|
9
|
+
export declare const hasRegistryComponent: (value: string) => boolean;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const NAME_COMPONENT = /^[a-z0-9]+(?:(?:[._]|__|[-]+)[a-z0-9]+)*$/;
|
|
2
|
+
const TAG_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$/;
|
|
3
|
+
const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
4
|
+
const REGISTRY_HOST_PATTERN = /^[a-zA-Z0-9.-]+(?::\d{1,5})?$/;
|
|
5
|
+
const isRegistryComponent = (component) => (component.includes(".") || component.includes(":") || component === "localhost")
|
|
6
|
+
&& REGISTRY_HOST_PATTERN.test(component);
|
|
7
|
+
const splitDigest = (value) => {
|
|
8
|
+
const index = value.indexOf("@");
|
|
9
|
+
if (index === -1) {
|
|
10
|
+
return { digest: null, rest: value };
|
|
11
|
+
}
|
|
12
|
+
return { digest: value.slice(index + 1), rest: value.slice(0, index) };
|
|
13
|
+
};
|
|
14
|
+
const splitTag = (value) => {
|
|
15
|
+
const slashIndex = value.lastIndexOf("/");
|
|
16
|
+
const colonIndex = value.lastIndexOf(":");
|
|
17
|
+
if (colonIndex === -1 || colonIndex < slashIndex) {
|
|
18
|
+
return { rest: value, tag: null };
|
|
19
|
+
}
|
|
20
|
+
return { rest: value.slice(0, colonIndex), tag: value.slice(colonIndex + 1) };
|
|
21
|
+
};
|
|
22
|
+
export const parseImageReference = (value) => {
|
|
23
|
+
const trimmed = value.trim();
|
|
24
|
+
if (!trimmed || /\s/.test(trimmed)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const { digest, rest: withoutDigest } = splitDigest(trimmed);
|
|
28
|
+
if (digest !== null && !DIGEST_PATTERN.test(digest)) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const { rest: repository, tag } = splitTag(withoutDigest);
|
|
32
|
+
if (tag !== null && !TAG_PATTERN.test(tag)) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (!repository) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const components = repository.split("/");
|
|
39
|
+
let registry = null;
|
|
40
|
+
let nameComponents = components;
|
|
41
|
+
if (components.length > 1 && isRegistryComponent(components[0])) {
|
|
42
|
+
registry = components[0];
|
|
43
|
+
nameComponents = components.slice(1);
|
|
44
|
+
}
|
|
45
|
+
if (nameComponents.length === 0 || nameComponents.some((component) => !NAME_COMPONENT.test(component))) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
digest,
|
|
50
|
+
name: nameComponents.join("/"),
|
|
51
|
+
registry,
|
|
52
|
+
tag
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
export const parseImplicitImageReference = (value) => {
|
|
56
|
+
const parsed = parseImageReference(value);
|
|
57
|
+
if (!parsed) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
if (parsed.digest === null && parsed.tag === null && parsed.registry === null) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return parsed;
|
|
64
|
+
};
|
|
65
|
+
export const hasRegistryComponent = (value) => parseImageReference(value)?.registry !== null;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ResolvedAuthProfile } from "../auth/index.js";
|
|
2
|
+
import type { DistributionReport } from "./types.js";
|
|
3
|
+
export interface ImageRuntimeAuthInput {
|
|
4
|
+
authProfile: ResolvedAuthProfile;
|
|
5
|
+
report: DistributionReport;
|
|
6
|
+
tempRoot: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ImageRuntimeAuthResult {
|
|
9
|
+
coveredModelSecrets: Set<string>;
|
|
10
|
+
mountArgs: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Builds the credential mounts for a sourceless image deployment that uses
|
|
14
|
+
* import-based model auth. The OAuth-mode config is already baked into the
|
|
15
|
+
* image, so this only mounts the consumer's credential tokens (per-adapter) and
|
|
16
|
+
* their raw import directories into each runtime home — the same material a
|
|
17
|
+
* project deployment provides, without needing the project source.
|
|
18
|
+
*/
|
|
19
|
+
export declare const prepareImageRuntimeAuthMounts: (input: ImageRuntimeAuthInput) => Promise<ImageRuntimeAuthResult>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { loadImportedClaudeCodeCredential, loadImportedCodexCredential } from "../auth/index.js";
|
|
3
|
+
import { fileExists } from "../filesystem/index.js";
|
|
4
|
+
import { getRuntimeAdapter } from "../runtime/index.js";
|
|
5
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
6
|
+
const importMountTargetName = (kind) => kind === "claude-code" ? ".claude" : ".codex";
|
|
7
|
+
/**
|
|
8
|
+
* Builds the credential mounts for a sourceless image deployment that uses
|
|
9
|
+
* import-based model auth. The OAuth-mode config is already baked into the
|
|
10
|
+
* image, so this only mounts the consumer's credential tokens (per-adapter) and
|
|
11
|
+
* their raw import directories into each runtime home — the same material a
|
|
12
|
+
* project deployment provides, without needing the project source.
|
|
13
|
+
*/
|
|
14
|
+
export const prepareImageRuntimeAuthMounts = async (input) => {
|
|
15
|
+
const coveredModelSecrets = new Set();
|
|
16
|
+
const mountArgs = [];
|
|
17
|
+
const runtimeHomes = new Set();
|
|
18
|
+
for (const instance of input.report.runtime_instances) {
|
|
19
|
+
if (instance.home_path) {
|
|
20
|
+
runtimeHomes.add(instance.home_path);
|
|
21
|
+
}
|
|
22
|
+
const adapter = getRuntimeAdapter(instance.runtime);
|
|
23
|
+
if (!adapter.prepareRuntimeAuth) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const prepared = await adapter.prepareRuntimeAuth({
|
|
27
|
+
authProfile: input.authProfile,
|
|
28
|
+
env: {},
|
|
29
|
+
instance: {
|
|
30
|
+
config_path: instance.config_path,
|
|
31
|
+
home_path: instance.home_path,
|
|
32
|
+
id: instance.id,
|
|
33
|
+
model_auth_methods: instance.model_auth_methods,
|
|
34
|
+
model_secrets_required: instance.model_secrets_required,
|
|
35
|
+
runtime: instance.runtime
|
|
36
|
+
},
|
|
37
|
+
outputDirectory: "",
|
|
38
|
+
tempRoot: input.tempRoot
|
|
39
|
+
});
|
|
40
|
+
for (const secret of prepared.coveredModelSecrets) {
|
|
41
|
+
coveredModelSecrets.add(secret);
|
|
42
|
+
}
|
|
43
|
+
mountArgs.push(...prepared.mountArgs);
|
|
44
|
+
}
|
|
45
|
+
// Mount the raw credential import directories into each runtime home so the
|
|
46
|
+
// runtime's OAuth client can read them (e.g. ~/.claude, ~/.codex).
|
|
47
|
+
for (const kind of ["claude-code", "codex"]) {
|
|
48
|
+
const entry = input.authProfile.imports[kind];
|
|
49
|
+
if (!entry) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (!(await fileExists(entry.path))) {
|
|
53
|
+
throw new SpawnfileError("validation_error", `Imported auth path for ${kind} does not exist: ${entry.path}`);
|
|
54
|
+
}
|
|
55
|
+
// The directory existing is not enough: a registered import whose credential
|
|
56
|
+
// file is missing, expired, or malformed would mount cleanly but produce a
|
|
57
|
+
// container that cannot authenticate. Load it now so the failure surfaces
|
|
58
|
+
// before the container starts, mirroring the project-deployment path.
|
|
59
|
+
const credential = kind === "claude-code"
|
|
60
|
+
? await loadImportedClaudeCodeCredential(entry.path)
|
|
61
|
+
: await loadImportedCodexCredential(entry.path);
|
|
62
|
+
if (!credential) {
|
|
63
|
+
throw new SpawnfileError("validation_error", `Imported ${kind} auth at ${entry.path} has no usable credential. ` +
|
|
64
|
+
`Re-run the ${kind} login (or re-import) before deploying this image.`);
|
|
65
|
+
}
|
|
66
|
+
for (const home of runtimeHomes) {
|
|
67
|
+
mountArgs.push("-v", `${entry.path}:${path.posix.join(home, importMountTargetName(kind))}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { coveredModelSecrets, mountArgs };
|
|
71
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export { buildDistributionReport, createDistributionImageLabels } from "./buildDistributionReport.js";
|
|
2
|
+
export type { BuildDistributionReportInput, DistributionEnvVariableInput } from "./buildDistributionReport.js";
|
|
3
|
+
export { createDistributionFingerprint } from "./fingerprint.js";
|
|
4
|
+
export { extractImageReport, resolveDockerBaseArgs } from "./extractImage.js";
|
|
5
|
+
export type { DockerCommandRunner, ExtractImageOptions, ImageInspection } from "./extractImage.js";
|
|
6
|
+
export { parseDistributionReport } from "./distributionReportSchema.js";
|
|
7
|
+
export { consumeImageUp } from "./consumeImage.js";
|
|
8
|
+
export type { ConsumeImageUpOptions, ConsumeImageUpResult } from "./consumeImage.js";
|
|
9
|
+
export { deriveDeploymentName, deriveVolumeName, renderEnvFileContent, resolveImageEnvironment } from "./consumeImageSupport.js";
|
|
10
|
+
export { createConsumerDockerRunner } from "./dockerRunner.js";
|
|
11
|
+
export { prepareImageRuntimeAuthMounts } from "./imageRuntimeAuth.js";
|
|
12
|
+
export { runImagePreflight } from "./preflight.js";
|
|
13
|
+
export { verifyDistributionReport } from "./verifyDistributionReport.js";
|
|
14
|
+
export type { VerifyDistributionReportInput } from "./verifyDistributionReport.js";
|
|
15
|
+
export { projectImageOrganizationView } from "./projectImageView.js";
|
|
16
|
+
export { buildImageInterfaceSummary, renderImageInterface } from "./renderImageInterface.js";
|
|
17
|
+
export type { ImageInterfaceSummary, RenderImageInterfaceOptions } from "./renderImageInterface.js";
|
|
18
|
+
export type { PreflightInput, PreflightResult } from "./preflight.js";
|
|
19
|
+
export { extractSingleFileFromTar } from "./tarReader.js";
|
|
20
|
+
export { hasRegistryComponent, parseImageReference, parseImplicitImageReference } from "./imageRef.js";
|
|
21
|
+
export type { ParsedImageReference } from "./imageRef.js";
|
|
22
|
+
export { normalizeProjectLabelSlug } from "./projectName.js";
|
|
23
|
+
export { DISTRIBUTION_REPORT_IMAGE_PATH, DISTRIBUTION_REPORT_OUTPUT_FILE, DISTRIBUTION_REPORT_VERSION, IMAGE_CONTRACT_VERSION } from "./types.js";
|
|
24
|
+
export type { DistributionAgentSummary, DistributionImageLabels, DistributionMoltnetNetwork, DistributionOrganizationSummary, DistributionPersistentMount, DistributionPortMapping, DistributionReport, DistributionRuntimeInstance, DistributionSecretCategory, DistributionSecretEntry, DistributionTeamSummary, DistributionWorkspaceResource } from "./types.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { buildDistributionReport, createDistributionImageLabels } from "./buildDistributionReport.js";
|
|
2
|
+
export { createDistributionFingerprint } from "./fingerprint.js";
|
|
3
|
+
export { extractImageReport, resolveDockerBaseArgs } from "./extractImage.js";
|
|
4
|
+
export { parseDistributionReport } from "./distributionReportSchema.js";
|
|
5
|
+
export { consumeImageUp } from "./consumeImage.js";
|
|
6
|
+
export { deriveDeploymentName, deriveVolumeName, renderEnvFileContent, resolveImageEnvironment } from "./consumeImageSupport.js";
|
|
7
|
+
export { createConsumerDockerRunner } from "./dockerRunner.js";
|
|
8
|
+
export { prepareImageRuntimeAuthMounts } from "./imageRuntimeAuth.js";
|
|
9
|
+
export { runImagePreflight } from "./preflight.js";
|
|
10
|
+
export { verifyDistributionReport } from "./verifyDistributionReport.js";
|
|
11
|
+
export { projectImageOrganizationView } from "./projectImageView.js";
|
|
12
|
+
export { buildImageInterfaceSummary, renderImageInterface } from "./renderImageInterface.js";
|
|
13
|
+
export { extractSingleFileFromTar } from "./tarReader.js";
|
|
14
|
+
export { hasRegistryComponent, parseImageReference, parseImplicitImageReference } from "./imageRef.js";
|
|
15
|
+
export { normalizeProjectLabelSlug } from "./projectName.js";
|
|
16
|
+
export { DISTRIBUTION_REPORT_IMAGE_PATH, DISTRIBUTION_REPORT_OUTPUT_FILE, DISTRIBUTION_REPORT_VERSION, IMAGE_CONTRACT_VERSION } from "./types.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DistributionReport } from "./types.js";
|
|
2
|
+
export interface PreflightInput {
|
|
3
|
+
authValues: Record<string, string>;
|
|
4
|
+
availableImports?: string[];
|
|
5
|
+
report: DistributionReport;
|
|
6
|
+
}
|
|
7
|
+
export interface PreflightResult {
|
|
8
|
+
generatedSecrets: string[];
|
|
9
|
+
requiredSecrets: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare const runImagePreflight: (input: PreflightInput) => PreflightResult;
|