quartermaster-code-runner 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. quartermaster_code_runner/__init__.py +38 -0
  2. quartermaster_code_runner/app.py +269 -0
  3. quartermaster_code_runner/config.py +175 -0
  4. quartermaster_code_runner/errors.py +88 -0
  5. quartermaster_code_runner/execution.py +231 -0
  6. quartermaster_code_runner/images.py +397 -0
  7. quartermaster_code_runner/runtime/bun/Dockerfile +22 -0
  8. quartermaster_code_runner/runtime/bun/completions.json +34 -0
  9. quartermaster_code_runner/runtime/bun/entrypoint.sh +32 -0
  10. quartermaster_code_runner/runtime/bun/sdk.ts +87 -0
  11. quartermaster_code_runner/runtime/deno/Dockerfile +22 -0
  12. quartermaster_code_runner/runtime/deno/completions.json +34 -0
  13. quartermaster_code_runner/runtime/deno/entrypoint.sh +32 -0
  14. quartermaster_code_runner/runtime/deno/sdk.ts +88 -0
  15. quartermaster_code_runner/runtime/go/Dockerfile +18 -0
  16. quartermaster_code_runner/runtime/go/completions.json +22 -0
  17. quartermaster_code_runner/runtime/go/entrypoint.sh +50 -0
  18. quartermaster_code_runner/runtime/go/sdk.go +101 -0
  19. quartermaster_code_runner/runtime/node/Dockerfile +31 -0
  20. quartermaster_code_runner/runtime/node/completions.json +34 -0
  21. quartermaster_code_runner/runtime/node/entrypoint.sh +33 -0
  22. quartermaster_code_runner/runtime/node/mcp-client.js +274 -0
  23. quartermaster_code_runner/runtime/node/sdk.js +109 -0
  24. quartermaster_code_runner/runtime/python/Dockerfile +42 -0
  25. quartermaster_code_runner/runtime/python/completions.json +34 -0
  26. quartermaster_code_runner/runtime/python/entrypoint.sh +30 -0
  27. quartermaster_code_runner/runtime/python/mcp-client.py +276 -0
  28. quartermaster_code_runner/runtime/python/sdk.py +103 -0
  29. quartermaster_code_runner/runtime/rust/Cargo.toml.default +9 -0
  30. quartermaster_code_runner/runtime/rust/Dockerfile +27 -0
  31. quartermaster_code_runner/runtime/rust/completions.json +34 -0
  32. quartermaster_code_runner/runtime/rust/entrypoint.sh +38 -0
  33. quartermaster_code_runner/runtime/rust/sdk/Cargo.toml +9 -0
  34. quartermaster_code_runner/runtime/rust/sdk/src/lib.rs +149 -0
  35. quartermaster_code_runner/schemas.py +154 -0
  36. quartermaster_code_runner/security.py +81 -0
  37. quartermaster_code_runner-0.0.1.dist-info/METADATA +322 -0
  38. quartermaster_code_runner-0.0.1.dist-info/RECORD +40 -0
  39. quartermaster_code_runner-0.0.1.dist-info/WHEEL +4 -0
  40. quartermaster_code_runner-0.0.1.dist-info/licenses/LICENSE +190 -0
@@ -0,0 +1,32 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ chown runner:runner /tmp
5
+ chown runner:runner /metadata 2>/dev/null || true
6
+
7
+ exec su -p -s /bin/sh -c '
8
+ set -e
9
+ cd /tmp
10
+ export HOME=/home/runner
11
+ export BUN_INSTALL=/home/runner/.bun
12
+
13
+ # Copy SDK for user code access
14
+ cp /app/sdk.ts /tmp/sdk.ts
15
+
16
+ # Decode code to default filename
17
+ if [ -n "$ENCODED_CODE" ]; then
18
+ echo "$ENCODED_CODE" | base64 -d > main.ts
19
+ fi
20
+
21
+ # Extract additional files if provided
22
+ if [ -n "$ENCODED_FILES" ]; then
23
+ echo "$ENCODED_FILES" | base64 -d | tar -xz
24
+ fi
25
+
26
+ # Execute custom entrypoint or default
27
+ if [ -n "$CUSTOM_ENTRYPOINT" ]; then
28
+ exec sh -c "$CUSTOM_ENTRYPOINT"
29
+ else
30
+ exec bun main.ts
31
+ fi
32
+ ' runner
@@ -0,0 +1,87 @@
1
+ /**
2
+ * quartermaster-code-runner SDK for Bun runtime.
3
+ */
4
+
5
+ const METADATA_FILE = "/metadata/.quartermaster_metadata.json";
6
+
7
+ /**
8
+ * Set the result metadata to be returned to the backend.
9
+ *
10
+ * This separates structured results from stdout/stderr logs.
11
+ *
12
+ * @param data - Any JSON-serializable data (object, array, string, number, etc.)
13
+ *
14
+ * @example
15
+ * import { setMetadata } from './sdk';
16
+ *
17
+ * const result = { status: 'success', count: 42 };
18
+ * setMetadata(result);
19
+ */
20
+ export function setMetadata(data: unknown): void {
21
+ Bun.write(METADATA_FILE, JSON.stringify(data));
22
+ }
23
+
24
+ /**
25
+ * Get previously set metadata (useful for reading/modifying).
26
+ *
27
+ * @returns The previously set metadata, or null if not set.
28
+ */
29
+ export function getMetadata(): unknown {
30
+ const file = Bun.file(METADATA_FILE);
31
+ if (!file.size) {
32
+ return null;
33
+ }
34
+ return JSON.parse(file.text() as unknown as string);
35
+ }
36
+
37
+ /**
38
+ * Load a file from the flow's environment.
39
+ * Only available during flow execution, not test runs.
40
+ *
41
+ * @param path - Path to the file within the environment.
42
+ * @returns The file content as a string.
43
+ */
44
+ export async function loadFile(path: string): Promise<string> {
45
+ const webdavUrl = process.env.QM_WEBDAV_URL;
46
+ if (!webdavUrl) {
47
+ throw new Error(
48
+ "loadFile() is only available during flow execution. " +
49
+ "For test runs, use mounted environments instead.",
50
+ );
51
+ }
52
+ const url = webdavUrl.replace(/\/?$/, "/") + path.replace(/^\//, "");
53
+ const resp = await fetch(url, { method: "GET" });
54
+ if (resp.status === 404) {
55
+ throw new Error(`File not found: ${path}`);
56
+ }
57
+ if (!resp.ok) {
58
+ throw new Error(`Failed to load file: HTTP ${resp.status}`);
59
+ }
60
+ return await resp.text();
61
+ }
62
+
63
+ /**
64
+ * Save a file to the flow's environment.
65
+ * Only available during flow execution, not test runs.
66
+ *
67
+ * @param path - Path to the file within the environment.
68
+ * @param content - The file content to save.
69
+ */
70
+ export async function saveFile(path: string, content: string): Promise<void> {
71
+ const webdavUrl = process.env.QM_WEBDAV_URL;
72
+ if (!webdavUrl) {
73
+ throw new Error(
74
+ "saveFile() is only available during flow execution. " +
75
+ "For test runs, use mounted environments instead.",
76
+ );
77
+ }
78
+ const url = webdavUrl.replace(/\/?$/, "/") + path.replace(/^\//, "");
79
+ const resp = await fetch(url, {
80
+ method: "PUT",
81
+ headers: { "Content-Type": "application/octet-stream" },
82
+ body: content,
83
+ });
84
+ if (!resp.ok) {
85
+ throw new Error(`Failed to save file: HTTP ${resp.status}`);
86
+ }
87
+ }
@@ -0,0 +1,22 @@
1
+ FROM denoland/deno:latest
2
+
3
+ LABEL qm.name="Deno"
4
+ LABEL qm.description="Deno runtime with TypeScript support"
5
+ LABEL qm.default_entrypoint="deno run main.ts"
6
+ LABEL qm.file_extension=".ts"
7
+ LABEL qm.main_file="main.ts"
8
+
9
+ WORKDIR /app
10
+
11
+ RUN apt-get update && \
12
+ apt-get install -y tar && \
13
+ rm -rf /var/lib/apt/lists/* && \
14
+ useradd --uid 1001 --no-create-home --shell /bin/sh runner && \
15
+ mkdir -p /home/runner/.cache/deno && \
16
+ chown -R runner:runner /home/runner
17
+
18
+ COPY entrypoint.sh .
19
+ COPY sdk.ts /app/sdk.ts
20
+ RUN chmod +x entrypoint.sh
21
+
22
+ ENTRYPOINT ["/app/entrypoint.sh"]
@@ -0,0 +1,34 @@
1
+ [
2
+ {
3
+ "caption": "import { setMetadata } from './sdk.ts'",
4
+ "value": "import { setMetadata } from './sdk.ts';",
5
+ "meta": "import",
6
+ "score": 1000
7
+ },
8
+ {
9
+ "caption": "import { getMetadata } from './sdk.ts'",
10
+ "value": "import { getMetadata } from './sdk.ts';",
11
+ "meta": "import",
12
+ "score": 1000
13
+ },
14
+ {
15
+ "caption": "import { setMetadata, getMetadata } from './sdk.ts'",
16
+ "value": "import { setMetadata, getMetadata } from './sdk.ts';",
17
+ "meta": "import",
18
+ "score": 1000
19
+ },
20
+ {
21
+ "caption": "setMetadata",
22
+ "snippet": "setMetadata($1)",
23
+ "meta": "sdk",
24
+ "score": 1000,
25
+ "docText": "Set structured result metadata to be returned to the backend. Accepts any JSON-serializable data."
26
+ },
27
+ {
28
+ "caption": "getMetadata",
29
+ "snippet": "getMetadata()",
30
+ "meta": "sdk",
31
+ "score": 1000,
32
+ "docText": "Get previously set metadata. Returns null if not set."
33
+ }
34
+ ]
@@ -0,0 +1,32 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ chown runner:runner /tmp
5
+ chown runner:runner /metadata 2>/dev/null || true
6
+
7
+ exec su -p -s /bin/sh -c '
8
+ set -e
9
+ cd /tmp
10
+ export HOME=/home/runner
11
+ export DENO_DIR=/home/runner/.cache/deno
12
+
13
+ # Copy SDK for user code access
14
+ cp /app/sdk.ts /tmp/sdk.ts
15
+
16
+ # Decode code to default filename
17
+ if [ -n "$ENCODED_CODE" ]; then
18
+ echo "$ENCODED_CODE" | base64 -d > main.ts
19
+ fi
20
+
21
+ # Extract additional files if provided
22
+ if [ -n "$ENCODED_FILES" ]; then
23
+ echo "$ENCODED_FILES" | base64 -d | tar -xz
24
+ fi
25
+
26
+ # Execute custom entrypoint or default (allow all permissions for simplicity)
27
+ if [ -n "$CUSTOM_ENTRYPOINT" ]; then
28
+ exec sh -c "$CUSTOM_ENTRYPOINT"
29
+ else
30
+ exec deno run --allow-all main.ts
31
+ fi
32
+ ' runner
@@ -0,0 +1,88 @@
1
+ /**
2
+ * quartermaster-code-runner SDK for Deno runtime.
3
+ */
4
+
5
+ const METADATA_FILE = "/metadata/.quartermaster_metadata.json";
6
+
7
+ /**
8
+ * Set the result metadata to be returned to the backend.
9
+ *
10
+ * This separates structured results from stdout/stderr logs.
11
+ *
12
+ * @param data - Any JSON-serializable data (object, array, string, number, etc.)
13
+ *
14
+ * @example
15
+ * import { setMetadata } from './sdk.ts';
16
+ *
17
+ * const result = { status: 'success', count: 42 };
18
+ * setMetadata(result);
19
+ */
20
+ export function setMetadata(data: unknown): void {
21
+ Deno.writeTextFileSync(METADATA_FILE, JSON.stringify(data));
22
+ }
23
+
24
+ /**
25
+ * Get previously set metadata (useful for reading/modifying).
26
+ *
27
+ * @returns The previously set metadata, or null if not set.
28
+ */
29
+ export function getMetadata(): unknown {
30
+ try {
31
+ const content = Deno.readTextFileSync(METADATA_FILE);
32
+ return JSON.parse(content);
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Load a file from the flow's environment.
40
+ * Only available during flow execution, not test runs.
41
+ *
42
+ * @param path - Path to the file within the environment.
43
+ * @returns The file content as a string.
44
+ */
45
+ export async function loadFile(path: string): Promise<string> {
46
+ const webdavUrl = Deno.env.get("QM_WEBDAV_URL");
47
+ if (!webdavUrl) {
48
+ throw new Error(
49
+ "loadFile() is only available during flow execution. " +
50
+ "For test runs, use mounted environments instead.",
51
+ );
52
+ }
53
+ const url = webdavUrl.replace(/\/?$/, "/") + path.replace(/^\//, "");
54
+ const resp = await fetch(url, { method: "GET" });
55
+ if (resp.status === 404) {
56
+ throw new Error(`File not found: ${path}`);
57
+ }
58
+ if (!resp.ok) {
59
+ throw new Error(`Failed to load file: HTTP ${resp.status}`);
60
+ }
61
+ return await resp.text();
62
+ }
63
+
64
+ /**
65
+ * Save a file to the flow's environment.
66
+ * Only available during flow execution, not test runs.
67
+ *
68
+ * @param path - Path to the file within the environment.
69
+ * @param content - The file content to save.
70
+ */
71
+ export async function saveFile(path: string, content: string): Promise<void> {
72
+ const webdavUrl = Deno.env.get("QM_WEBDAV_URL");
73
+ if (!webdavUrl) {
74
+ throw new Error(
75
+ "saveFile() is only available during flow execution. " +
76
+ "For test runs, use mounted environments instead.",
77
+ );
78
+ }
79
+ const url = webdavUrl.replace(/\/?$/, "/") + path.replace(/^\//, "");
80
+ const resp = await fetch(url, {
81
+ method: "PUT",
82
+ headers: { "Content-Type": "application/octet-stream" },
83
+ body: content,
84
+ });
85
+ if (!resp.ok) {
86
+ throw new Error(`Failed to save file: HTTP ${resp.status}`);
87
+ }
88
+ }
@@ -0,0 +1,18 @@
1
+ FROM golang:1.22-alpine
2
+
3
+ LABEL qm.name="Go"
4
+ LABEL qm.description="Go 1.22 runtime"
5
+ LABEL qm.default_entrypoint="go run main.go"
6
+ LABEL qm.file_extension=".go"
7
+ LABEL qm.main_file="main.go"
8
+
9
+ WORKDIR /app
10
+
11
+ RUN apk add --no-cache tar && \
12
+ adduser -D -u 1001 -s /bin/sh runner
13
+
14
+ COPY entrypoint.sh .
15
+ COPY sdk.go .
16
+ RUN chmod +x entrypoint.sh
17
+
18
+ ENTRYPOINT ["/app/entrypoint.sh"]
@@ -0,0 +1,22 @@
1
+ [
2
+ {
3
+ "caption": "import \"sdk\"",
4
+ "value": "import \"sdk\"",
5
+ "meta": "import",
6
+ "score": 1000
7
+ },
8
+ {
9
+ "caption": "sdk.SetMetadata",
10
+ "snippet": "sdk.SetMetadata($1)",
11
+ "meta": "sdk",
12
+ "score": 1000,
13
+ "docText": "Set structured result metadata to be returned to the backend. Accepts any JSON-serializable data."
14
+ },
15
+ {
16
+ "caption": "sdk.GetMetadata",
17
+ "snippet": "sdk.GetMetadata(&$1)",
18
+ "meta": "sdk",
19
+ "score": 1000,
20
+ "docText": "Get previously set metadata into the provided destination. Returns nil if no metadata has been set."
21
+ }
22
+ ]
@@ -0,0 +1,50 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ chown runner:runner /tmp
5
+ chown runner:runner /metadata 2>/dev/null || true
6
+ chown runner:runner /workspace 2>/dev/null || true
7
+
8
+ exec su -p -s /bin/sh -c '
9
+ set -e
10
+ cd /workspace
11
+ export HOME=/workspace
12
+ export GOCACHE=/workspace/.cache/go-build
13
+ export GOFLAGS="-buildvcs=false"
14
+ mkdir -p $GOCACHE
15
+
16
+ # Copy SDK for user code access and create go module structure
17
+ mkdir -p /workspace/sdk
18
+ cp /app/sdk.go /workspace/sdk/sdk.go
19
+
20
+ # Decode code to default filename
21
+ if [ -n "$ENCODED_CODE" ]; then
22
+ echo "$ENCODED_CODE" | base64 -d > main.go
23
+ fi
24
+
25
+ # Extract additional files if provided
26
+ if [ -n "$ENCODED_FILES" ]; then
27
+ echo "$ENCODED_FILES" | base64 -d | tar -xz
28
+ fi
29
+
30
+ # Create go.mod for the user code with local SDK
31
+ cat > go.mod << EOF
32
+ module main
33
+ go 1.22
34
+ require sdk v0.0.0
35
+ replace sdk => ./sdk
36
+ EOF
37
+
38
+ # Create go.mod for the SDK
39
+ cat > /workspace/sdk/go.mod << EOF
40
+ module sdk
41
+ go 1.22
42
+ EOF
43
+
44
+ # Execute custom entrypoint or default
45
+ if [ -n "$CUSTOM_ENTRYPOINT" ]; then
46
+ exec sh -c "$CUSTOM_ENTRYPOINT"
47
+ else
48
+ exec go run main.go
49
+ fi
50
+ ' runner
@@ -0,0 +1,101 @@
1
+ // Package sdk provides quartermaster-code-runner SDK for Go runtime.
2
+ package sdk
3
+
4
+ import (
5
+ "encoding/json"
6
+ "errors"
7
+ "fmt"
8
+ "io"
9
+ "net/http"
10
+ "os"
11
+ "strings"
12
+ )
13
+
14
+ const metadataFile = "/metadata/.quartermaster_metadata.json"
15
+
16
+ // SetMetadata sets the result metadata to be returned to the backend.
17
+ //
18
+ // This separates structured results from stdout/stderr logs.
19
+ //
20
+ // Example:
21
+ //
22
+ // import "sdk"
23
+ //
24
+ // result := map[string]interface{}{"status": "success", "count": 42}
25
+ // sdk.SetMetadata(result)
26
+ func SetMetadata(data interface{}) error {
27
+ jsonData, err := json.Marshal(data)
28
+ if err != nil {
29
+ return err
30
+ }
31
+ return os.WriteFile(metadataFile, jsonData, 0644)
32
+ }
33
+
34
+ // GetMetadata gets previously set metadata (useful for reading/modifying).
35
+ //
36
+ // Returns nil if no metadata has been set.
37
+ func GetMetadata(dest interface{}) error {
38
+ data, err := os.ReadFile(metadataFile)
39
+ if err != nil {
40
+ if os.IsNotExist(err) {
41
+ return nil
42
+ }
43
+ return err
44
+ }
45
+ return json.Unmarshal(data, dest)
46
+ }
47
+
48
+ var errNoWebDAV = errors.New(
49
+ "LoadFile/SaveFile is only available during flow execution. " +
50
+ "For test runs, use mounted environments instead",
51
+ )
52
+
53
+ // LoadFile loads a file from the flow's environment.
54
+ // Only available during flow execution, not test runs.
55
+ func LoadFile(path string) (string, error) {
56
+ webdavURL := os.Getenv("QM_WEBDAV_URL")
57
+ if webdavURL == "" {
58
+ return "", errNoWebDAV
59
+ }
60
+ url := strings.TrimRight(webdavURL, "/") + "/" + strings.TrimLeft(path, "/")
61
+ resp, err := http.Get(url)
62
+ if err != nil {
63
+ return "", fmt.Errorf("failed to load file: %w", err)
64
+ }
65
+ defer resp.Body.Close()
66
+ if resp.StatusCode == 404 {
67
+ return "", fmt.Errorf("file not found: %s", path)
68
+ }
69
+ if resp.StatusCode < 200 || resp.StatusCode >= 300 {
70
+ return "", fmt.Errorf("failed to load file: HTTP %d", resp.StatusCode)
71
+ }
72
+ body, err := io.ReadAll(resp.Body)
73
+ if err != nil {
74
+ return "", fmt.Errorf("failed to read response: %w", err)
75
+ }
76
+ return string(body), nil
77
+ }
78
+
79
+ // SaveFile saves a file to the flow's environment.
80
+ // Only available during flow execution, not test runs.
81
+ func SaveFile(path, content string) error {
82
+ webdavURL := os.Getenv("QM_WEBDAV_URL")
83
+ if webdavURL == "" {
84
+ return errNoWebDAV
85
+ }
86
+ url := strings.TrimRight(webdavURL, "/") + "/" + strings.TrimLeft(path, "/")
87
+ req, err := http.NewRequest("PUT", url, strings.NewReader(content))
88
+ if err != nil {
89
+ return fmt.Errorf("failed to create request: %w", err)
90
+ }
91
+ req.Header.Set("Content-Type", "application/octet-stream")
92
+ resp, err := http.DefaultClient.Do(req)
93
+ if err != nil {
94
+ return fmt.Errorf("failed to save file: %w", err)
95
+ }
96
+ defer resp.Body.Close()
97
+ if resp.StatusCode < 200 || resp.StatusCode >= 300 {
98
+ return fmt.Errorf("failed to save file: HTTP %d", resp.StatusCode)
99
+ }
100
+ return nil
101
+ }
@@ -0,0 +1,31 @@
1
+ FROM node:22-slim
2
+
3
+ LABEL qm.name="Node.js"
4
+ LABEL qm.description="Node.js 22 with npm, npx, and bun"
5
+ LABEL qm.default_entrypoint="node main.js"
6
+ LABEL qm.file_extension=".js"
7
+ LABEL qm.main_file="main.js"
8
+
9
+ WORKDIR /app
10
+
11
+ RUN apt-get update && \
12
+ apt-get install -y tar curl unzip git && \
13
+ rm -rf /var/lib/apt/lists/* && \
14
+ useradd --uid 1001 --no-create-home --shell /bin/sh runner && \
15
+ mkdir -p /home/runner/.npm /home/runner/.bun && \
16
+ chown -R runner:runner /home/runner
17
+
18
+ # Install bun globally accessible
19
+ ENV BUN_INSTALL=/usr/local
20
+ RUN curl -fsSL https://bun.sh/install | bash
21
+
22
+ # Pre-install common packages globally for faster startup
23
+ RUN npm install -g typescript ts-node
24
+ RUN npm install -g @gongrzhe/server-gmail-autoauth-mcp
25
+
26
+ COPY entrypoint.sh .
27
+ COPY sdk.js .
28
+ COPY mcp-client.js .
29
+ RUN chmod +x entrypoint.sh
30
+
31
+ ENTRYPOINT ["/app/entrypoint.sh"]
@@ -0,0 +1,34 @@
1
+ [
2
+ {
3
+ "caption": "const { setMetadata } = require('./sdk')",
4
+ "value": "const { setMetadata } = require('./sdk');",
5
+ "meta": "import",
6
+ "score": 1000
7
+ },
8
+ {
9
+ "caption": "const { getMetadata } = require('./sdk')",
10
+ "value": "const { getMetadata } = require('./sdk');",
11
+ "meta": "import",
12
+ "score": 1000
13
+ },
14
+ {
15
+ "caption": "const { setMetadata, getMetadata } = require('./sdk')",
16
+ "value": "const { setMetadata, getMetadata } = require('./sdk');",
17
+ "meta": "import",
18
+ "score": 1000
19
+ },
20
+ {
21
+ "caption": "setMetadata",
22
+ "snippet": "setMetadata($1)",
23
+ "meta": "sdk",
24
+ "score": 1000,
25
+ "docText": "Set structured result metadata to be returned to the backend. Accepts any JSON-serializable data."
26
+ },
27
+ {
28
+ "caption": "getMetadata",
29
+ "snippet": "getMetadata()",
30
+ "meta": "sdk",
31
+ "score": 1000,
32
+ "docText": "Get previously set metadata. Returns null if not set."
33
+ }
34
+ ]
@@ -0,0 +1,33 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ chown runner:runner /tmp
5
+ chown runner:runner /metadata 2>/dev/null || true
6
+
7
+ exec su -p -s /bin/sh -c '
8
+ set -e
9
+ cd /tmp
10
+ export HOME=/home/runner
11
+ export NPM_CONFIG_CACHE=/tmp/.npm
12
+
13
+ # Copy SDK and MCP client for user code access
14
+ cp /app/sdk.js /tmp/sdk.js
15
+ cp /app/mcp-client.js /tmp/mcp-client.js
16
+
17
+ # Decode code to default filename
18
+ if [ -n "$ENCODED_CODE" ]; then
19
+ echo "$ENCODED_CODE" | base64 -d > main.js
20
+ fi
21
+
22
+ # Extract additional files if provided
23
+ if [ -n "$ENCODED_FILES" ]; then
24
+ echo "$ENCODED_FILES" | base64 -d | tar -xz
25
+ fi
26
+
27
+ # Execute custom entrypoint or default
28
+ if [ -n "$CUSTOM_ENTRYPOINT" ]; then
29
+ exec sh -c "$CUSTOM_ENTRYPOINT"
30
+ else
31
+ exec node main.js
32
+ fi
33
+ ' runner