spawnfile 0.1.0 → 0.1.1
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 +11 -11
- package/package.json +7 -2
- package/dist/.env.example +0 -5
- package/dist/Dockerfile +0 -21
- package/dist/compiler/discordSurface.d.ts +0 -4
- package/dist/compiler/discordSurface.js +0 -28
- package/dist/container/rootfs/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/config.json +0 -16
- package/dist/container/rootfs/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/workspace/AGENTS.md +0 -1
- package/dist/e2e/cli.d.ts +0 -1
- package/dist/e2e/cli.js +0 -40
- package/dist/e2e/dockerAuth.d.ts +0 -18
- package/dist/e2e/dockerAuth.js +0 -212
- package/dist/e2e/fixtures.d.ts +0 -2
- package/dist/e2e/fixtures.js +0 -49
- package/dist/e2e/index.d.ts +0 -4
- package/dist/e2e/index.js +0 -4
- package/dist/e2e/runtimePrompts.d.ts +0 -13
- package/dist/e2e/runtimePrompts.js +0 -132
- package/dist/e2e/scenarios.d.ts +0 -3
- package/dist/e2e/scenarios.js +0 -84
- package/dist/e2e/types.d.ts +0 -35
- package/dist/e2e/types.js +0 -1
- package/dist/entrypoint.sh +0 -71
- package/dist/runtimes/picoclaw/agents/assistant/config.json +0 -16
- package/dist/runtimes/picoclaw/agents/assistant/workspace/AGENTS.md +0 -1
- package/dist/spawnfile-report.json +0 -71
package/README.md
CHANGED
|
@@ -54,31 +54,31 @@ Spawnfile v0.1 is implemented as a Node.js CLI in TypeScript.
|
|
|
54
54
|
- manifest parsing: `yaml` + `zod`
|
|
55
55
|
- tests: `vitest`
|
|
56
56
|
|
|
57
|
-
That gives us fast iteration
|
|
57
|
+
That gives us fast iteration and a conventional `bin`-based CLI published to [npm](https://www.npmjs.com/package/spawnfile).
|
|
58
58
|
|
|
59
59
|
---
|
|
60
60
|
|
|
61
61
|
## Install
|
|
62
62
|
|
|
63
|
-
From a repository checkout:
|
|
64
|
-
|
|
65
63
|
```bash
|
|
66
|
-
|
|
67
|
-
npm install
|
|
68
|
-
npm run build
|
|
69
|
-
npm link
|
|
64
|
+
npm install -g spawnfile
|
|
70
65
|
```
|
|
71
66
|
|
|
72
|
-
|
|
67
|
+
Verify:
|
|
73
68
|
|
|
74
69
|
```bash
|
|
75
|
-
|
|
70
|
+
spawnfile --help
|
|
76
71
|
```
|
|
77
72
|
|
|
78
|
-
|
|
73
|
+
### From source
|
|
79
74
|
|
|
80
75
|
```bash
|
|
81
|
-
spawnfile
|
|
76
|
+
git clone https://github.com/noopolis/spawnfile.git
|
|
77
|
+
cd spawnfile
|
|
78
|
+
nvm use
|
|
79
|
+
npm install
|
|
80
|
+
npm run build
|
|
81
|
+
npm link
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
To clone the target runtimes and generate reference blueprints:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spawnfile",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Canonical source compiler for autonomous agents and teams.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -16,10 +16,15 @@
|
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
18
|
"blueprints": "./scripts/blueprints.sh",
|
|
19
|
-
"build": "tsc --project tsconfig.build.json && node ./scripts/copy-runtime-scaffold-assets.mjs",
|
|
19
|
+
"build": "rm -rf dist && tsc --project tsconfig.build.json && node ./scripts/copy-runtime-scaffold-assets.mjs",
|
|
20
20
|
"clean": "rm -rf coverage dist",
|
|
21
21
|
"coverage": "vitest run --coverage",
|
|
22
22
|
"dev": "tsx src/cli/index.ts",
|
|
23
|
+
"prepack": "npm run build",
|
|
24
|
+
"prepublishOnly": "npm run typecheck && npm test",
|
|
25
|
+
"release:patch": "npm version patch && npm publish",
|
|
26
|
+
"release:minor": "npm version minor && npm publish",
|
|
27
|
+
"release:major": "npm version major && npm publish",
|
|
23
28
|
"runtimes": "./scripts/runtimes.sh",
|
|
24
29
|
"runtimes:sync": "./scripts/runtimes.sh && ./scripts/blueprints.sh",
|
|
25
30
|
"test:e2e:docker-auth": "tsx src/e2e/cli.ts",
|
package/dist/.env.example
DELETED
package/dist/Dockerfile
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
FROM debian:bookworm-slim
|
|
2
|
-
USER root
|
|
3
|
-
|
|
4
|
-
WORKDIR /opt/spawnfile
|
|
5
|
-
RUN apt-get update && apt-get install -y --no-install-recommends bash ca-certificates curl nodejs npm tar && rm -rf /var/lib/apt/lists/*
|
|
6
|
-
|
|
7
|
-
RUN npm install -g --omit=dev --no-fund --no-audit @anthropic-ai/claude-code @openai/codex
|
|
8
|
-
|
|
9
|
-
RUN mkdir -p /opt/spawnfile/runtime-installs/picoclaw/bin
|
|
10
|
-
RUN arch="$(dpkg --print-architecture)" && case "$arch" in amd64) asset="picoclaw_Linux_x86_64.tar.gz" ;; arm64) asset="picoclaw_Linux_arm64.tar.gz" ;; *) echo "Unsupported PicoClaw release architecture: $arch" >&2; exit 1 ;; esac && url="https://github.com/sipeed/picoclaw/releases/download/v0.2.3/$asset" && curl -fsSL -o /tmp/picoclaw.tar.gz "$url" && rm -rf /tmp/picoclaw-extract && mkdir -p /tmp/picoclaw-extract && tar -xzf /tmp/picoclaw.tar.gz -C /tmp/picoclaw-extract && binary_path="$(find /tmp/picoclaw-extract -type f -name "picoclaw" | head -n 1)" && [ -n "$binary_path" ] && install -m 0755 "$binary_path" /opt/spawnfile/runtime-installs/picoclaw/bin/picoclaw && ln -sf /opt/spawnfile/runtime-installs/picoclaw/bin/picoclaw /usr/local/bin/picoclaw && rm -rf /tmp/picoclaw.tar.gz /tmp/picoclaw-extract
|
|
11
|
-
|
|
12
|
-
RUN if ! id -u spawnfile >/dev/null 2>&1; then useradd --create-home --home-dir /home/spawnfile --shell /bin/bash spawnfile; fi
|
|
13
|
-
|
|
14
|
-
COPY container/rootfs/ /
|
|
15
|
-
COPY .env.example /opt/spawnfile/.env.example
|
|
16
|
-
COPY entrypoint.sh /opt/spawnfile/entrypoint.sh
|
|
17
|
-
RUN chmod +x /opt/spawnfile/entrypoint.sh
|
|
18
|
-
RUN mkdir -p /var/lib/spawnfile && chown -R spawnfile:spawnfile /var/lib/spawnfile /opt/spawnfile
|
|
19
|
-
EXPOSE 18790
|
|
20
|
-
USER spawnfile
|
|
21
|
-
ENTRYPOINT ["/opt/spawnfile/entrypoint.sh"]
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { SurfacesBlock } from "../manifest/index.js";
|
|
2
|
-
import type { ResolvedAgentSurfaces } from "./types.js";
|
|
3
|
-
export declare const resolveAgentSurfaces: (surfaces: SurfacesBlock | undefined) => ResolvedAgentSurfaces | undefined;
|
|
4
|
-
export declare const listAgentSurfaceSecretNames: (surfaces: ResolvedAgentSurfaces | undefined) => string[];
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_DISCORD_BOT_TOKEN_SECRET } from "../shared/index.js";
|
|
2
|
-
export const resolveAgentSurfaces = (surfaces) => {
|
|
3
|
-
if (!surfaces?.discord) {
|
|
4
|
-
return undefined;
|
|
5
|
-
}
|
|
6
|
-
return {
|
|
7
|
-
discord: {
|
|
8
|
-
...(surfaces.discord.access
|
|
9
|
-
? {
|
|
10
|
-
access: {
|
|
11
|
-
channels: [...(surfaces.discord.access.channels ?? [])],
|
|
12
|
-
guilds: [...(surfaces.discord.access.guilds ?? [])],
|
|
13
|
-
mode: surfaces.discord.access.mode ??
|
|
14
|
-
"allowlist",
|
|
15
|
-
users: [...(surfaces.discord.access.users ?? [])]
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
: {}),
|
|
19
|
-
botTokenSecret: surfaces.discord.bot_token_secret ?? DEFAULT_DISCORD_BOT_TOKEN_SECRET
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
export const listAgentSurfaceSecretNames = (surfaces) => {
|
|
24
|
-
if (!surfaces?.discord) {
|
|
25
|
-
return [];
|
|
26
|
-
}
|
|
27
|
-
return [surfaces.discord.botTokenSecret];
|
|
28
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"agents": {
|
|
3
|
-
"defaults": {
|
|
4
|
-
"workspace": "/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/workspace",
|
|
5
|
-
"restrict_to_workspace": true,
|
|
6
|
-
"model_name": "claude-sonnet-4.6"
|
|
7
|
-
}
|
|
8
|
-
},
|
|
9
|
-
"model_list": [
|
|
10
|
-
{
|
|
11
|
-
"api_key": "file://secrets/ANTHROPIC_API_KEY",
|
|
12
|
-
"model_name": "claude-sonnet-4.6",
|
|
13
|
-
"model": "anthropic/claude-sonnet-4.6"
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
You are a concise assistant. Reply with exactly the requested token and nothing else.
|
package/dist/e2e/cli.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/e2e/cli.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { isSpawnfileError } from "../shared/index.js";
|
|
3
|
-
import { runDockerAuthE2E } from "./dockerAuth.js";
|
|
4
|
-
const collect = (value, previous) => [...previous, value];
|
|
5
|
-
const main = async () => {
|
|
6
|
-
const program = new Command();
|
|
7
|
-
program
|
|
8
|
-
.name("spawnfile-e2e")
|
|
9
|
-
.description("Run opt-in Docker auth E2E scenarios against real runtime images")
|
|
10
|
-
.option("--scenario <id>", "Scenario id to run", collect, [])
|
|
11
|
-
.option("--runtime <runtime>", "Runtime filter", collect, [])
|
|
12
|
-
.option("--auth <method>", "Auth method filter", collect, [])
|
|
13
|
-
.option("--env-file <path>", "Env file for api_key scenarios")
|
|
14
|
-
.option("--claude-from <directory>", "Claude Code config directory override")
|
|
15
|
-
.option("--codex-from <directory>", "Codex config directory override")
|
|
16
|
-
.option("--keep-artifacts", "Keep temporary projects and compile output")
|
|
17
|
-
.option("--keep-images", "Keep built Docker images after each scenario");
|
|
18
|
-
await program.parseAsync(process.argv);
|
|
19
|
-
const options = program.opts();
|
|
20
|
-
const result = await runDockerAuthE2E({
|
|
21
|
-
authMethods: options.auth,
|
|
22
|
-
claudeCodeDirectory: options.claudeFrom,
|
|
23
|
-
codexDirectory: options.codexFrom,
|
|
24
|
-
envFilePath: options.envFile,
|
|
25
|
-
keepArtifacts: options.keepArtifacts,
|
|
26
|
-
keepImages: options.keepImages,
|
|
27
|
-
runtimes: options.runtime,
|
|
28
|
-
scenarioIds: options.scenario
|
|
29
|
-
});
|
|
30
|
-
console.log(`Docker auth E2E passed (${result.results.length} scenarios)`);
|
|
31
|
-
};
|
|
32
|
-
main().catch((error) => {
|
|
33
|
-
const message = isSpawnfileError(error)
|
|
34
|
-
? `${error.code}: ${error.message}`
|
|
35
|
-
: error instanceof Error
|
|
36
|
-
? error.message
|
|
37
|
-
: String(error);
|
|
38
|
-
process.stderr.write(`${message}\n`);
|
|
39
|
-
process.exitCode = 1;
|
|
40
|
-
});
|
package/dist/e2e/dockerAuth.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { DockerAuthE2EFilters, DockerAuthE2EScenarioResult } from "./types.js";
|
|
2
|
-
export interface DockerAuthE2ELogger {
|
|
3
|
-
error(message: string): void;
|
|
4
|
-
info(message: string): void;
|
|
5
|
-
}
|
|
6
|
-
export interface RunDockerAuthE2EOptions extends DockerAuthE2EFilters {
|
|
7
|
-
claudeCodeDirectory?: string;
|
|
8
|
-
codexDirectory?: string;
|
|
9
|
-
dockerCommand?: string;
|
|
10
|
-
envFilePath?: string;
|
|
11
|
-
keepArtifacts?: boolean;
|
|
12
|
-
keepImages?: boolean;
|
|
13
|
-
logger?: DockerAuthE2ELogger;
|
|
14
|
-
}
|
|
15
|
-
export interface RunDockerAuthE2EResult {
|
|
16
|
-
results: DockerAuthE2EScenarioResult[];
|
|
17
|
-
}
|
|
18
|
-
export declare const runDockerAuthE2E: (options?: RunDockerAuthE2EOptions) => Promise<RunDockerAuthE2EResult>;
|
package/dist/e2e/dockerAuth.js
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
import os from "node:os";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { mkdtemp } from "node:fs/promises";
|
|
4
|
-
import { requireAuthProfile } from "../auth/index.js";
|
|
5
|
-
import { buildProject, createDockerRunInvocation, runDockerContainer, syncProjectAuth } from "../compiler/index.js";
|
|
6
|
-
import { removeDirectory } from "../filesystem/index.js";
|
|
7
|
-
import { SpawnfileError } from "../shared/index.js";
|
|
8
|
-
import { materializeDockerAuthFixture } from "./fixtures.js";
|
|
9
|
-
import { waitForRuntimeReady, promptRuntime } from "./runtimePrompts.js";
|
|
10
|
-
import { filterDockerAuthE2EScenarios } from "./scenarios.js";
|
|
11
|
-
const DEFAULT_ENV_FILE = path.resolve(process.cwd(), "../headhunter/.env");
|
|
12
|
-
const createLogger = (logger) => logger ?? {
|
|
13
|
-
error: (message) => console.error(message),
|
|
14
|
-
info: (message) => console.log(message)
|
|
15
|
-
};
|
|
16
|
-
const createScenarioImageTag = (scenario) => `spawnfile-e2e-${scenario.id}-${Date.now()}`;
|
|
17
|
-
const createScenarioPrompt = (scenarioId, runtime) => `Reply with exactly SF-E2E-${scenarioId.toUpperCase()}-${runtime.toUpperCase()} and nothing else.`;
|
|
18
|
-
const extractSentinel = (prompt) => prompt.replace("Reply with exactly ", "").replace(" and nothing else.", "");
|
|
19
|
-
const findPromptInstance = (scenario, instances, runtime) => {
|
|
20
|
-
const runtimeInstances = instances.filter((instance) => instance.runtime === runtime);
|
|
21
|
-
if (runtimeInstances.length === 0) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
if (scenario.kind === "single-agent") {
|
|
25
|
-
return runtimeInstances[0] ?? null;
|
|
26
|
-
}
|
|
27
|
-
return runtimeInstances[0] ?? null;
|
|
28
|
-
};
|
|
29
|
-
const resolveEnvFilePath = (inputPath) => inputPath ?? DEFAULT_ENV_FILE;
|
|
30
|
-
const withSpawnfileHome = async (spawnfileHome, fn) => {
|
|
31
|
-
const previousValue = process.env.SPAWNFILE_HOME;
|
|
32
|
-
process.env.SPAWNFILE_HOME = spawnfileHome;
|
|
33
|
-
try {
|
|
34
|
-
return await fn();
|
|
35
|
-
}
|
|
36
|
-
finally {
|
|
37
|
-
if (typeof previousValue === "string") {
|
|
38
|
-
process.env.SPAWNFILE_HOME = previousValue;
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
delete process.env.SPAWNFILE_HOME;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
const runDockerCommand = async (dockerCommand, args) => {
|
|
46
|
-
const { spawn } = await import("node:child_process");
|
|
47
|
-
return new Promise((resolve, reject) => {
|
|
48
|
-
const child = spawn(dockerCommand, args, {
|
|
49
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
50
|
-
});
|
|
51
|
-
const stdout = [];
|
|
52
|
-
const stderr = [];
|
|
53
|
-
child.stdout.on("data", (chunk) => stdout.push(String(chunk)));
|
|
54
|
-
child.stderr.on("data", (chunk) => stderr.push(String(chunk)));
|
|
55
|
-
child.once("error", (error) => {
|
|
56
|
-
reject(new SpawnfileError("runtime_error", `Unable to start docker command ${dockerCommand}: ${error.message}`));
|
|
57
|
-
});
|
|
58
|
-
child.once("exit", (code, signal) => {
|
|
59
|
-
if (code === 0) {
|
|
60
|
-
resolve(stdout.join("").trim());
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
reject(new SpawnfileError("runtime_error", signal
|
|
64
|
-
? `Docker command exited from signal ${signal}: ${dockerCommand} ${args.join(" ")}`
|
|
65
|
-
: `Docker command failed with exit code ${code ?? "unknown"}: ${dockerCommand} ${args.join(" ")}\n${stderr.join("")}`.trim()));
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
};
|
|
69
|
-
const cleanupDockerArtifacts = async (dockerCommand, containerName, imageTag, options) => {
|
|
70
|
-
try {
|
|
71
|
-
await runDockerCommand(dockerCommand, ["rm", "-f", containerName]);
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
// Ignore best-effort cleanup failures.
|
|
75
|
-
}
|
|
76
|
-
if (options.keepImages) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
try {
|
|
80
|
-
await runDockerCommand(dockerCommand, ["image", "rm", "-f", imageTag]);
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
// Ignore best-effort cleanup failures.
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
const readDockerLogs = async (dockerCommand, containerName) => {
|
|
87
|
-
try {
|
|
88
|
-
return await runDockerCommand(dockerCommand, ["logs", containerName]);
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
return "";
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
const runScenario = async (scenario, options) => {
|
|
95
|
-
const startedAt = Date.now();
|
|
96
|
-
const logger = createLogger(options.logger);
|
|
97
|
-
const scenarioRoot = await mkdtemp(path.join(os.tmpdir(), `spawnfile-e2e-${scenario.id}-`));
|
|
98
|
-
const projectDirectory = path.join(scenarioRoot, "project");
|
|
99
|
-
const outputDirectory = path.join(scenarioRoot, "dist");
|
|
100
|
-
const spawnfileHome = path.join(scenarioRoot, "spawnfile-home");
|
|
101
|
-
const profileName = "e2e";
|
|
102
|
-
const imageTag = createScenarioImageTag(scenario);
|
|
103
|
-
const containerName = `spawnfile-e2e-${scenario.id}`;
|
|
104
|
-
logger.info(`scenario ${scenario.id}: materializing fixture`);
|
|
105
|
-
try {
|
|
106
|
-
await materializeDockerAuthFixture(scenario, projectDirectory);
|
|
107
|
-
await withSpawnfileHome(spawnfileHome, async () => {
|
|
108
|
-
logger.info(`scenario ${scenario.id}: syncing auth`);
|
|
109
|
-
await syncProjectAuth(projectDirectory, {
|
|
110
|
-
claudeCodeDirectory: options.claudeCodeDirectory,
|
|
111
|
-
codexDirectory: options.codexDirectory,
|
|
112
|
-
envFilePath: resolveEnvFilePath(options.envFilePath),
|
|
113
|
-
profileName
|
|
114
|
-
});
|
|
115
|
-
logger.info(`scenario ${scenario.id}: building image ${imageTag}`);
|
|
116
|
-
const buildResult = await buildProject(projectDirectory, {
|
|
117
|
-
dockerCommand: options.dockerCommand,
|
|
118
|
-
imageTag,
|
|
119
|
-
outputDirectory
|
|
120
|
-
});
|
|
121
|
-
const runtimeInstances = buildResult.report.container?.runtime_instances ?? [];
|
|
122
|
-
const authProfile = await requireAuthProfile(profileName);
|
|
123
|
-
const invocation = await createDockerRunInvocation(buildResult, imageTag, {
|
|
124
|
-
authProfile,
|
|
125
|
-
containerName,
|
|
126
|
-
detach: true,
|
|
127
|
-
dockerCommand: options.dockerCommand
|
|
128
|
-
});
|
|
129
|
-
try {
|
|
130
|
-
logger.info(`scenario ${scenario.id}: starting container ${containerName}`);
|
|
131
|
-
await runDockerContainer(invocation);
|
|
132
|
-
for (const check of scenario.promptChecks) {
|
|
133
|
-
const prompt = createScenarioPrompt(scenario.id, check.runtime);
|
|
134
|
-
const sentinel = extractSentinel(prompt);
|
|
135
|
-
const promptInstance = findPromptInstance(scenario, runtimeInstances, check.runtime);
|
|
136
|
-
logger.info(`scenario ${scenario.id}: waiting for ${check.runtime}`);
|
|
137
|
-
await waitForRuntimeReady(check.runtime);
|
|
138
|
-
logger.info(`scenario ${scenario.id}: prompting ${check.runtime}`);
|
|
139
|
-
const output = await promptRuntime(check.runtime, {
|
|
140
|
-
agentName: check.agentName,
|
|
141
|
-
command: options.dockerCommand,
|
|
142
|
-
configPath: promptInstance?.config_path,
|
|
143
|
-
containerName,
|
|
144
|
-
homePath: promptInstance?.home_path ?? undefined,
|
|
145
|
-
prompt
|
|
146
|
-
});
|
|
147
|
-
if (!output.includes(sentinel)) {
|
|
148
|
-
throw new SpawnfileError("runtime_error", `Scenario ${scenario.id} did not return sentinel ${sentinel} for ${check.runtime}`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
const logs = await readDockerLogs(options.dockerCommand, containerName);
|
|
154
|
-
throw new SpawnfileError("runtime_error", `${error instanceof Error ? error.message : String(error)}${logs ? `\n\nDocker logs:\n${logs}` : ""}`);
|
|
155
|
-
}
|
|
156
|
-
finally {
|
|
157
|
-
await cleanupDockerArtifacts(options.dockerCommand, containerName, imageTag, {
|
|
158
|
-
keepImages: options.keepImages
|
|
159
|
-
});
|
|
160
|
-
await removeDirectory(invocation.supportDirectory);
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
if (!options.keepArtifacts) {
|
|
164
|
-
await removeDirectory(scenarioRoot);
|
|
165
|
-
}
|
|
166
|
-
return {
|
|
167
|
-
durationMs: Date.now() - startedAt,
|
|
168
|
-
id: scenario.id,
|
|
169
|
-
success: true
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
catch (error) {
|
|
173
|
-
if (!options.keepArtifacts) {
|
|
174
|
-
await removeDirectory(scenarioRoot);
|
|
175
|
-
}
|
|
176
|
-
return {
|
|
177
|
-
durationMs: Date.now() - startedAt,
|
|
178
|
-
errorMessage: error instanceof Error ? error.message : String(error),
|
|
179
|
-
id: scenario.id,
|
|
180
|
-
success: false
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
const formatFailures = (results) => results
|
|
185
|
-
.filter((result) => !result.success)
|
|
186
|
-
.map((result) => `- ${result.id}: ${result.errorMessage ?? "unknown error"}`)
|
|
187
|
-
.join("\n");
|
|
188
|
-
export const runDockerAuthE2E = async (options = {}) => {
|
|
189
|
-
const logger = createLogger(options.logger);
|
|
190
|
-
const scenarios = filterDockerAuthE2EScenarios(options);
|
|
191
|
-
if (scenarios.length === 0) {
|
|
192
|
-
throw new SpawnfileError("validation_error", "No Docker auth E2E scenarios matched the filter");
|
|
193
|
-
}
|
|
194
|
-
const results = [];
|
|
195
|
-
for (const scenario of scenarios) {
|
|
196
|
-
const result = await runScenario(scenario, {
|
|
197
|
-
claudeCodeDirectory: options.claudeCodeDirectory,
|
|
198
|
-
codexDirectory: options.codexDirectory,
|
|
199
|
-
dockerCommand: options.dockerCommand ?? "docker",
|
|
200
|
-
envFilePath: options.envFilePath,
|
|
201
|
-
keepArtifacts: options.keepArtifacts ?? false,
|
|
202
|
-
keepImages: options.keepImages ?? false,
|
|
203
|
-
logger
|
|
204
|
-
});
|
|
205
|
-
results.push(result);
|
|
206
|
-
logger.info(`${result.success ? "PASS" : "FAIL"} ${result.id} (${Math.round(result.durationMs / 1000)}s)`);
|
|
207
|
-
}
|
|
208
|
-
if (results.some((result) => !result.success)) {
|
|
209
|
-
throw new SpawnfileError("runtime_error", `Docker auth E2E failed:\n${formatFailures(results)}`);
|
|
210
|
-
}
|
|
211
|
-
return { results };
|
|
212
|
-
};
|
package/dist/e2e/fixtures.d.ts
DELETED
package/dist/e2e/fixtures.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
import YAML from "yaml";
|
|
4
|
-
import { copyDirectory, readUtf8File, writeUtf8File } from "../filesystem/index.js";
|
|
5
|
-
const FIXTURES_ROOT = fileURLToPath(new URL("../../fixtures/e2e", import.meta.url));
|
|
6
|
-
const readYamlFile = async (filePath) => YAML.parse(await readUtf8File(filePath));
|
|
7
|
-
const writeYamlFile = async (filePath, value) => {
|
|
8
|
-
await writeUtf8File(filePath, YAML.stringify(value));
|
|
9
|
-
};
|
|
10
|
-
const applyAgentSpec = (manifest, spec) => ({
|
|
11
|
-
...manifest,
|
|
12
|
-
execution: {
|
|
13
|
-
...(manifest.execution ?? {}),
|
|
14
|
-
model: {
|
|
15
|
-
...((manifest.execution?.model ?? {})),
|
|
16
|
-
auth: {
|
|
17
|
-
method: spec.authMethod
|
|
18
|
-
},
|
|
19
|
-
primary: {
|
|
20
|
-
name: spec.modelName,
|
|
21
|
-
provider: spec.provider
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
kind: "agent",
|
|
26
|
-
name: spec.name,
|
|
27
|
-
runtime: spec.runtime
|
|
28
|
-
});
|
|
29
|
-
const patchSingleAgentFixture = async (destinationDirectory, scenario) => {
|
|
30
|
-
const manifestPath = path.join(destinationDirectory, "Spawnfile");
|
|
31
|
-
const manifest = await readYamlFile(manifestPath);
|
|
32
|
-
await writeYamlFile(manifestPath, applyAgentSpec(manifest, scenario.agents[0]));
|
|
33
|
-
};
|
|
34
|
-
const patchTeamFixture = async (destinationDirectory, scenario) => {
|
|
35
|
-
for (const agent of scenario.agents) {
|
|
36
|
-
const manifestPath = path.join(destinationDirectory, "agents", agent.directoryName, "Spawnfile");
|
|
37
|
-
const manifest = await readYamlFile(manifestPath);
|
|
38
|
-
await writeYamlFile(manifestPath, applyAgentSpec(manifest, agent));
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
export const materializeDockerAuthFixture = async (scenario, destinationDirectory) => {
|
|
42
|
-
const sourceDirectory = path.join(FIXTURES_ROOT, scenario.fixture);
|
|
43
|
-
await copyDirectory(sourceDirectory, destinationDirectory);
|
|
44
|
-
if (scenario.fixture === "agent") {
|
|
45
|
-
await patchSingleAgentFixture(destinationDirectory, scenario);
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
await patchTeamFixture(destinationDirectory, scenario);
|
|
49
|
-
};
|
package/dist/e2e/index.d.ts
DELETED
package/dist/e2e/index.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { E2ERuntime } from "./types.js";
|
|
2
|
-
interface RuntimePromptOptions {
|
|
3
|
-
agentName?: string;
|
|
4
|
-
command?: string;
|
|
5
|
-
configPath?: string;
|
|
6
|
-
containerName: string;
|
|
7
|
-
homePath?: string;
|
|
8
|
-
prompt: string;
|
|
9
|
-
timeoutMs?: number;
|
|
10
|
-
}
|
|
11
|
-
export declare const waitForRuntimeReady: (runtime: E2ERuntime, timeoutMs?: number) => Promise<void>;
|
|
12
|
-
export declare const promptRuntime: (runtime: E2ERuntime, options: RuntimePromptOptions) => Promise<string>;
|
|
13
|
-
export {};
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { SpawnfileError } from "../shared/index.js";
|
|
3
|
-
const wait = async (delayMs) => new Promise((resolve) => {
|
|
4
|
-
setTimeout(resolve, delayMs);
|
|
5
|
-
});
|
|
6
|
-
const runCommand = async (command, args, timeoutMs = 180_000) => new Promise((resolve, reject) => {
|
|
7
|
-
const child = spawn(command, args, {
|
|
8
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
9
|
-
});
|
|
10
|
-
const stdout = [];
|
|
11
|
-
const stderr = [];
|
|
12
|
-
const timer = setTimeout(() => {
|
|
13
|
-
child.kill("SIGTERM");
|
|
14
|
-
reject(new SpawnfileError("runtime_error", `Command timed out after ${timeoutMs}ms: ${command} ${args.join(" ")}`));
|
|
15
|
-
}, timeoutMs);
|
|
16
|
-
child.stdout.on("data", (chunk) => {
|
|
17
|
-
stdout.push(String(chunk));
|
|
18
|
-
});
|
|
19
|
-
child.stderr.on("data", (chunk) => {
|
|
20
|
-
stderr.push(String(chunk));
|
|
21
|
-
});
|
|
22
|
-
child.once("error", (error) => {
|
|
23
|
-
clearTimeout(timer);
|
|
24
|
-
reject(new SpawnfileError("runtime_error", `Unable to start command ${command}: ${error.message}`));
|
|
25
|
-
});
|
|
26
|
-
child.once("exit", (code, signal) => {
|
|
27
|
-
clearTimeout(timer);
|
|
28
|
-
if (code === 0) {
|
|
29
|
-
resolve({
|
|
30
|
-
stderr: stderr.join(""),
|
|
31
|
-
stdout: stdout.join("")
|
|
32
|
-
});
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
reject(new SpawnfileError("runtime_error", signal
|
|
36
|
-
? `Command exited from signal ${signal}: ${command} ${args.join(" ")}`
|
|
37
|
-
: `Command failed with exit code ${code ?? "unknown"}: ${command} ${args.join(" ")}\n${stderr.join("")}`.trim()));
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
const fetchText = async (url) => {
|
|
41
|
-
const response = await fetch(url);
|
|
42
|
-
return {
|
|
43
|
-
body: await response.text(),
|
|
44
|
-
status: response.status
|
|
45
|
-
};
|
|
46
|
-
};
|
|
47
|
-
const getHealthUrl = (runtime) => runtime === "openclaw"
|
|
48
|
-
? "http://127.0.0.1:18789/healthz"
|
|
49
|
-
: runtime === "picoclaw"
|
|
50
|
-
? "http://127.0.0.1:18790/health"
|
|
51
|
-
: "http://127.0.0.1:3777/api/agents";
|
|
52
|
-
export const waitForRuntimeReady = async (runtime, timeoutMs = 120_000) => {
|
|
53
|
-
const startedAt = Date.now();
|
|
54
|
-
const url = getHealthUrl(runtime);
|
|
55
|
-
while (Date.now() - startedAt <= timeoutMs) {
|
|
56
|
-
try {
|
|
57
|
-
const response = await fetch(url);
|
|
58
|
-
if (response.ok) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
// Ignore readiness races and keep polling.
|
|
64
|
-
}
|
|
65
|
-
await wait(2_000);
|
|
66
|
-
}
|
|
67
|
-
throw new SpawnfileError("runtime_error", `Runtime ${runtime} did not become ready within ${timeoutMs}ms (${url})`);
|
|
68
|
-
};
|
|
69
|
-
const promptOpenClaw = async (options) => {
|
|
70
|
-
const result = await runCommand(options.command ?? "docker", [
|
|
71
|
-
"exec",
|
|
72
|
-
"-u",
|
|
73
|
-
"0",
|
|
74
|
-
...(options.homePath ? ["-e", `OPENCLAW_HOME=${options.homePath}`] : []),
|
|
75
|
-
...(options.configPath ? ["-e", `OPENCLAW_CONFIG_PATH=${options.configPath}`] : []),
|
|
76
|
-
options.containerName,
|
|
77
|
-
"openclaw",
|
|
78
|
-
"agent",
|
|
79
|
-
"--local",
|
|
80
|
-
"--agent",
|
|
81
|
-
"main",
|
|
82
|
-
"--message",
|
|
83
|
-
options.prompt,
|
|
84
|
-
"--json"
|
|
85
|
-
], options.timeoutMs);
|
|
86
|
-
return `${result.stdout}\n${result.stderr}`;
|
|
87
|
-
};
|
|
88
|
-
const promptPicoClaw = async (options) => {
|
|
89
|
-
const result = await runCommand(options.command ?? "docker", [
|
|
90
|
-
"exec",
|
|
91
|
-
...(options.homePath ? ["-e", `HOME=${options.homePath}`, "-e", `PICOCLAW_HOME=${options.homePath}`] : []),
|
|
92
|
-
...(options.configPath ? ["-e", `PICOCLAW_CONFIG=${options.configPath}`] : []),
|
|
93
|
-
options.containerName,
|
|
94
|
-
"picoclaw",
|
|
95
|
-
"agent",
|
|
96
|
-
"-m",
|
|
97
|
-
options.prompt
|
|
98
|
-
], options.timeoutMs);
|
|
99
|
-
return `${result.stdout}\n${result.stderr}`;
|
|
100
|
-
};
|
|
101
|
-
const promptTinyClaw = async (options) => {
|
|
102
|
-
const enqueueResponse = await fetch("http://127.0.0.1:3777/api/message", {
|
|
103
|
-
body: JSON.stringify({
|
|
104
|
-
...(options.agentName ? { agent: options.agentName } : {}),
|
|
105
|
-
channel: "spawnfile-e2e",
|
|
106
|
-
message: options.prompt,
|
|
107
|
-
sender: "spawnfile-e2e"
|
|
108
|
-
}),
|
|
109
|
-
headers: {
|
|
110
|
-
"Content-Type": "application/json"
|
|
111
|
-
},
|
|
112
|
-
method: "POST"
|
|
113
|
-
});
|
|
114
|
-
if (!enqueueResponse.ok) {
|
|
115
|
-
throw new SpawnfileError("runtime_error", `TinyClaw enqueue failed with status ${enqueueResponse.status}`);
|
|
116
|
-
}
|
|
117
|
-
const startedAt = Date.now();
|
|
118
|
-
const timeoutMs = options.timeoutMs ?? 180_000;
|
|
119
|
-
while (Date.now() - startedAt <= timeoutMs) {
|
|
120
|
-
const { body, status } = await fetchText("http://127.0.0.1:3777/api/responses?limit=20");
|
|
121
|
-
if (status === 200 && body.includes(options.prompt.replace("Reply with exactly ", "").replace(" and nothing else.", ""))) {
|
|
122
|
-
return body;
|
|
123
|
-
}
|
|
124
|
-
await wait(2_000);
|
|
125
|
-
}
|
|
126
|
-
throw new SpawnfileError("runtime_error", `TinyClaw did not return a response within ${timeoutMs}ms`);
|
|
127
|
-
};
|
|
128
|
-
export const promptRuntime = async (runtime, options) => runtime === "openclaw"
|
|
129
|
-
? promptOpenClaw(options)
|
|
130
|
-
: runtime === "picoclaw"
|
|
131
|
-
? promptPicoClaw(options)
|
|
132
|
-
: promptTinyClaw(options);
|
package/dist/e2e/scenarios.d.ts
DELETED
package/dist/e2e/scenarios.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
const createSingleAgentScenario = (runtime, provider, modelName, authMethod) => {
|
|
2
|
-
const id = `${runtime}-${authMethod}`;
|
|
3
|
-
const agent = {
|
|
4
|
-
authMethod,
|
|
5
|
-
directoryName: runtime,
|
|
6
|
-
modelName,
|
|
7
|
-
name: `${runtime}-assistant`,
|
|
8
|
-
provider,
|
|
9
|
-
runtime
|
|
10
|
-
};
|
|
11
|
-
return {
|
|
12
|
-
agents: [agent],
|
|
13
|
-
description: `${runtime} single-agent Docker auth smoke using ${authMethod}`,
|
|
14
|
-
fixture: "agent",
|
|
15
|
-
id,
|
|
16
|
-
kind: "single-agent",
|
|
17
|
-
promptChecks: [{ runtime }]
|
|
18
|
-
};
|
|
19
|
-
};
|
|
20
|
-
const SINGLE_AGENT_SCENARIOS = [
|
|
21
|
-
createSingleAgentScenario("openclaw", "openai", "gpt-5", "api_key"),
|
|
22
|
-
createSingleAgentScenario("openclaw", "openai", "gpt-5", "codex"),
|
|
23
|
-
createSingleAgentScenario("openclaw", "anthropic", "claude-sonnet-4-5", "claude-code"),
|
|
24
|
-
createSingleAgentScenario("picoclaw", "openai", "gpt-5", "api_key"),
|
|
25
|
-
createSingleAgentScenario("picoclaw", "openai", "gpt-5", "codex"),
|
|
26
|
-
createSingleAgentScenario("picoclaw", "anthropic", "claude-sonnet-4-5", "claude-code"),
|
|
27
|
-
createSingleAgentScenario("tinyclaw", "openai", "gpt-5", "codex"),
|
|
28
|
-
createSingleAgentScenario("tinyclaw", "anthropic", "claude-sonnet-4-5", "claude-code")
|
|
29
|
-
];
|
|
30
|
-
const TEAM_SCENARIOS = [
|
|
31
|
-
{
|
|
32
|
-
agents: [
|
|
33
|
-
{
|
|
34
|
-
authMethod: "codex",
|
|
35
|
-
directoryName: "openclaw",
|
|
36
|
-
modelName: "gpt-5",
|
|
37
|
-
name: "openclaw",
|
|
38
|
-
provider: "openai",
|
|
39
|
-
runtime: "openclaw"
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
authMethod: "api_key",
|
|
43
|
-
directoryName: "picoclaw",
|
|
44
|
-
modelName: "gpt-5",
|
|
45
|
-
name: "picoclaw",
|
|
46
|
-
provider: "openai",
|
|
47
|
-
runtime: "picoclaw"
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
authMethod: "codex",
|
|
51
|
-
directoryName: "tinyclaw",
|
|
52
|
-
modelName: "gpt-5",
|
|
53
|
-
name: "tinyclaw",
|
|
54
|
-
provider: "openai",
|
|
55
|
-
runtime: "tinyclaw"
|
|
56
|
-
}
|
|
57
|
-
],
|
|
58
|
-
description: "multi-runtime Docker auth smoke team",
|
|
59
|
-
fixture: "team",
|
|
60
|
-
id: "team-multi-runtime",
|
|
61
|
-
kind: "team",
|
|
62
|
-
promptChecks: [
|
|
63
|
-
{ runtime: "openclaw" },
|
|
64
|
-
{ runtime: "picoclaw" },
|
|
65
|
-
{ agentName: "tinyclaw", runtime: "tinyclaw" }
|
|
66
|
-
]
|
|
67
|
-
}
|
|
68
|
-
];
|
|
69
|
-
export const listDockerAuthE2EScenarios = () => [
|
|
70
|
-
...SINGLE_AGENT_SCENARIOS,
|
|
71
|
-
...TEAM_SCENARIOS
|
|
72
|
-
];
|
|
73
|
-
const includesScenarioId = (filters, scenario) => !filters.scenarioIds ||
|
|
74
|
-
filters.scenarioIds.length === 0 ||
|
|
75
|
-
filters.scenarioIds.includes(scenario.id);
|
|
76
|
-
const includesAuthMethod = (filters, scenario) => !filters.authMethods ||
|
|
77
|
-
filters.authMethods.length === 0 ||
|
|
78
|
-
scenario.agents.some((agent) => filters.authMethods.includes(agent.authMethod));
|
|
79
|
-
const includesRuntime = (filters, scenario) => !filters.runtimes ||
|
|
80
|
-
filters.runtimes.length === 0 ||
|
|
81
|
-
scenario.agents.some((agent) => filters.runtimes.includes(agent.runtime));
|
|
82
|
-
export const filterDockerAuthE2EScenarios = (filters = {}) => listDockerAuthE2EScenarios().filter((scenario) => includesScenarioId(filters, scenario) &&
|
|
83
|
-
includesAuthMethod(filters, scenario) &&
|
|
84
|
-
includesRuntime(filters, scenario));
|
package/dist/e2e/types.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { ModelAuthMethod } from "../shared/index.js";
|
|
2
|
-
export type E2ERuntime = "openclaw" | "picoclaw" | "tinyclaw";
|
|
3
|
-
export type E2EFixtureKind = "agent" | "team";
|
|
4
|
-
export type E2EScenarioKind = "single-agent" | "team";
|
|
5
|
-
export interface E2EAgentSpec {
|
|
6
|
-
authMethod: ModelAuthMethod;
|
|
7
|
-
directoryName: string;
|
|
8
|
-
modelName: string;
|
|
9
|
-
name: string;
|
|
10
|
-
provider: string;
|
|
11
|
-
runtime: E2ERuntime;
|
|
12
|
-
}
|
|
13
|
-
export interface E2EPromptCheck {
|
|
14
|
-
agentName?: string;
|
|
15
|
-
runtime: E2ERuntime;
|
|
16
|
-
}
|
|
17
|
-
export interface DockerAuthE2EScenario {
|
|
18
|
-
agents: E2EAgentSpec[];
|
|
19
|
-
description: string;
|
|
20
|
-
fixture: E2EFixtureKind;
|
|
21
|
-
id: string;
|
|
22
|
-
kind: E2EScenarioKind;
|
|
23
|
-
promptChecks: E2EPromptCheck[];
|
|
24
|
-
}
|
|
25
|
-
export interface DockerAuthE2EFilters {
|
|
26
|
-
authMethods?: ModelAuthMethod[];
|
|
27
|
-
runtimes?: E2ERuntime[];
|
|
28
|
-
scenarioIds?: string[];
|
|
29
|
-
}
|
|
30
|
-
export interface DockerAuthE2EScenarioResult {
|
|
31
|
-
durationMs: number;
|
|
32
|
-
errorMessage?: string;
|
|
33
|
-
id: string;
|
|
34
|
-
success: boolean;
|
|
35
|
-
}
|
package/dist/e2e/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/entrypoint.sh
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
require_env() {
|
|
5
|
-
local name="$1"
|
|
6
|
-
if [ -z "${!name:-}" ]; then
|
|
7
|
-
echo "Missing required env: $name" >&2
|
|
8
|
-
exit 1
|
|
9
|
-
fi
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
require_file() {
|
|
13
|
-
local target="$1"
|
|
14
|
-
if [ ! -f "$target" ]; then
|
|
15
|
-
echo "Missing required file: $target" >&2
|
|
16
|
-
exit 1
|
|
17
|
-
fi
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
write_env_file() {
|
|
21
|
-
local name="$1"
|
|
22
|
-
local target="$2"
|
|
23
|
-
if [ -z "${!name:-}" ]; then
|
|
24
|
-
return
|
|
25
|
-
fi
|
|
26
|
-
mkdir -p "$(dirname "$target")"
|
|
27
|
-
printf %s "${!name:-}" > "$target"
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
apply_json_env_value() {
|
|
31
|
-
local target="$1"
|
|
32
|
-
local name="$2"
|
|
33
|
-
local json_path="$3"
|
|
34
|
-
if [ -z "${!name:-}" ]; then
|
|
35
|
-
return
|
|
36
|
-
fi
|
|
37
|
-
python3 - "$target" "$name" "$json_path" <<'PY'
|
|
38
|
-
import json
|
|
39
|
-
import os
|
|
40
|
-
import sys
|
|
41
|
-
|
|
42
|
-
target_path = sys.argv[1]
|
|
43
|
-
env_name = sys.argv[2]
|
|
44
|
-
json_path = sys.argv[3].split('.')
|
|
45
|
-
value = os.environ.get(env_name)
|
|
46
|
-
if value is None:
|
|
47
|
-
raise SystemExit(0)
|
|
48
|
-
|
|
49
|
-
with open(target_path, encoding='utf-8') as handle:
|
|
50
|
-
data = json.load(handle)
|
|
51
|
-
|
|
52
|
-
cursor = data
|
|
53
|
-
for part in json_path[:-1]:
|
|
54
|
-
child = cursor.get(part)
|
|
55
|
-
if not isinstance(child, dict):
|
|
56
|
-
child = {}
|
|
57
|
-
cursor[part] = child
|
|
58
|
-
cursor = child
|
|
59
|
-
|
|
60
|
-
cursor[json_path[-1]] = value
|
|
61
|
-
|
|
62
|
-
with open(target_path, 'w', encoding='utf-8') as handle:
|
|
63
|
-
json.dump(data, handle, indent=2)
|
|
64
|
-
handle.write('\n')
|
|
65
|
-
PY
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
mkdir -p '/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/workspace'
|
|
69
|
-
require_file '/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/config.json'
|
|
70
|
-
write_env_file 'ANTHROPIC_API_KEY' '/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/secrets/ANTHROPIC_API_KEY'
|
|
71
|
-
HOME='/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw' PICOCLAW_HOME='/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw' PICOCLAW_CONFIG='/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/config.json' PICOCLAW_GATEWAY_PORT='18790' PICOCLAW_GATEWAY_HOST='0.0.0.0' exec 'picoclaw' 'gateway' '--allow-empty'
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"agents": {
|
|
3
|
-
"defaults": {
|
|
4
|
-
"workspace": "<workspace-path>",
|
|
5
|
-
"restrict_to_workspace": true,
|
|
6
|
-
"model_name": "claude-sonnet-4.6"
|
|
7
|
-
}
|
|
8
|
-
},
|
|
9
|
-
"model_list": [
|
|
10
|
-
{
|
|
11
|
-
"api_key": "file://secrets/ANTHROPIC_API_KEY",
|
|
12
|
-
"model_name": "claude-sonnet-4.6",
|
|
13
|
-
"model": "anthropic/claude-sonnet-4.6"
|
|
14
|
-
}
|
|
15
|
-
]
|
|
16
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
You are a concise assistant. Reply with exactly the requested token and nothing else.
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"container": {
|
|
3
|
-
"dockerfile": "Dockerfile",
|
|
4
|
-
"entrypoint": "entrypoint.sh",
|
|
5
|
-
"env_example": ".env.example",
|
|
6
|
-
"model_secrets_required": [
|
|
7
|
-
"ANTHROPIC_API_KEY"
|
|
8
|
-
],
|
|
9
|
-
"ports": [
|
|
10
|
-
18790
|
|
11
|
-
],
|
|
12
|
-
"runtime_instances": [
|
|
13
|
-
{
|
|
14
|
-
"config_path": "/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw/config.json",
|
|
15
|
-
"home_path": "/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw",
|
|
16
|
-
"id": "agent-assistant",
|
|
17
|
-
"model_secrets_required": [
|
|
18
|
-
"ANTHROPIC_API_KEY"
|
|
19
|
-
],
|
|
20
|
-
"runtime": "picoclaw"
|
|
21
|
-
}
|
|
22
|
-
],
|
|
23
|
-
"runtime_homes": [
|
|
24
|
-
"/var/lib/spawnfile/instances/picoclaw/agent-assistant/picoclaw"
|
|
25
|
-
],
|
|
26
|
-
"runtime_secrets_required": [],
|
|
27
|
-
"runtimes_installed": [
|
|
28
|
-
"picoclaw"
|
|
29
|
-
],
|
|
30
|
-
"secrets_required": [
|
|
31
|
-
"ANTHROPIC_API_KEY"
|
|
32
|
-
]
|
|
33
|
-
},
|
|
34
|
-
"diagnostics": [],
|
|
35
|
-
"nodes": [
|
|
36
|
-
{
|
|
37
|
-
"capabilities": [
|
|
38
|
-
{
|
|
39
|
-
"key": "docs.system",
|
|
40
|
-
"message": "",
|
|
41
|
-
"outcome": "supported"
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
"key": "execution.model",
|
|
45
|
-
"message": "",
|
|
46
|
-
"outcome": "supported"
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
"key": "execution.workspace",
|
|
50
|
-
"message": "",
|
|
51
|
-
"outcome": "supported"
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
"key": "execution.sandbox",
|
|
55
|
-
"message": "",
|
|
56
|
-
"outcome": "supported"
|
|
57
|
-
}
|
|
58
|
-
],
|
|
59
|
-
"diagnostics": [],
|
|
60
|
-
"id": "agent:assistant",
|
|
61
|
-
"kind": "agent",
|
|
62
|
-
"output_dir": "runtimes/picoclaw/agents/assistant",
|
|
63
|
-
"runtime": "picoclaw",
|
|
64
|
-
"runtime_ref": "v0.2.3",
|
|
65
|
-
"runtime_status": "active",
|
|
66
|
-
"source": "/tmp/spawnfile-e2e/picoclaw-anthropic/Spawnfile"
|
|
67
|
-
}
|
|
68
|
-
],
|
|
69
|
-
"root": "/tmp/spawnfile-e2e/picoclaw-anthropic/Spawnfile",
|
|
70
|
-
"spawnfile_version": "0.1"
|
|
71
|
-
}
|