context-mode 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +29 -0
- package/.mcp.json +8 -0
- package/LICENSE +21 -0
- package/README.md +304 -0
- package/build/cli.d.ts +10 -0
- package/build/cli.js +193 -0
- package/build/executor.d.ts +27 -0
- package/build/executor.js +255 -0
- package/build/runtime.d.ts +24 -0
- package/build/runtime.js +167 -0
- package/build/server.d.ts +2 -0
- package/build/server.js +457 -0
- package/build/store.d.ts +39 -0
- package/build/store.js +212 -0
- package/package.json +64 -0
- package/skills/context-mode/SKILL.md +124 -0
- package/skills/context-mode/references/anti-patterns.md +257 -0
- package/skills/context-mode/references/patterns-javascript.md +298 -0
- package/skills/context-mode/references/patterns-python.md +304 -0
- package/skills/context-mode/references/patterns-shell.md +277 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
var _a;
|
|
2
|
+
import { spawn, execSync } from "node:child_process";
|
|
3
|
+
import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { detectRuntimes, buildCommand, } from "./runtime.js";
|
|
7
|
+
export class PolyglotExecutor {
|
|
8
|
+
#maxOutputBytes;
|
|
9
|
+
#projectRoot;
|
|
10
|
+
#runtimes;
|
|
11
|
+
constructor(opts) {
|
|
12
|
+
this.#maxOutputBytes = opts?.maxOutputBytes ?? 102_400;
|
|
13
|
+
this.#projectRoot = opts?.projectRoot ?? process.cwd();
|
|
14
|
+
this.#runtimes = opts?.runtimes ?? detectRuntimes();
|
|
15
|
+
}
|
|
16
|
+
get runtimes() {
|
|
17
|
+
return { ...this.#runtimes };
|
|
18
|
+
}
|
|
19
|
+
async execute(opts) {
|
|
20
|
+
const { language, code, timeout = 30_000 } = opts;
|
|
21
|
+
const tmpDir = mkdtempSync(join(tmpdir(), "ctx-mode-"));
|
|
22
|
+
try {
|
|
23
|
+
const filePath = this.#writeScript(tmpDir, code, language);
|
|
24
|
+
const cmd = buildCommand(this.#runtimes, language, filePath);
|
|
25
|
+
// Rust: compile then run
|
|
26
|
+
if (cmd[0] === "__rust_compile_run__") {
|
|
27
|
+
return await this.#compileAndRun(filePath, tmpDir, timeout);
|
|
28
|
+
}
|
|
29
|
+
return await this.#spawn(cmd, tmpDir, timeout);
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async executeFile(opts) {
|
|
36
|
+
const { path: filePath, language, code, timeout = 30_000 } = opts;
|
|
37
|
+
const absolutePath = resolve(this.#projectRoot, filePath);
|
|
38
|
+
const wrappedCode = this.#wrapWithFileContent(absolutePath, language, code);
|
|
39
|
+
return this.execute({ language, code: wrappedCode, timeout });
|
|
40
|
+
}
|
|
41
|
+
#writeScript(tmpDir, code, language) {
|
|
42
|
+
const extMap = {
|
|
43
|
+
javascript: "js",
|
|
44
|
+
typescript: "ts",
|
|
45
|
+
python: "py",
|
|
46
|
+
shell: "sh",
|
|
47
|
+
ruby: "rb",
|
|
48
|
+
go: "go",
|
|
49
|
+
rust: "rs",
|
|
50
|
+
php: "php",
|
|
51
|
+
perl: "pl",
|
|
52
|
+
r: "R",
|
|
53
|
+
};
|
|
54
|
+
// Go needs a main package wrapper if not present
|
|
55
|
+
if (language === "go" && !code.includes("package ")) {
|
|
56
|
+
code = `package main\n\nimport "fmt"\n\nfunc main() {\n${code}\n}\n`;
|
|
57
|
+
}
|
|
58
|
+
// PHP needs opening tag if not present
|
|
59
|
+
if (language === "php" && !code.trimStart().startsWith("<?")) {
|
|
60
|
+
code = `<?php\n${code}`;
|
|
61
|
+
}
|
|
62
|
+
const fp = join(tmpDir, `script.${extMap[language]}`);
|
|
63
|
+
if (language === "shell") {
|
|
64
|
+
writeFileSync(fp, code, { encoding: "utf-8", mode: 0o700 });
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
writeFileSync(fp, code, "utf-8");
|
|
68
|
+
}
|
|
69
|
+
return fp;
|
|
70
|
+
}
|
|
71
|
+
async #compileAndRun(srcPath, cwd, timeout) {
|
|
72
|
+
const binPath = srcPath.replace(/\.rs$/, "");
|
|
73
|
+
// Compile
|
|
74
|
+
try {
|
|
75
|
+
execSync(`rustc ${srcPath} -o ${binPath} 2>&1`, {
|
|
76
|
+
cwd,
|
|
77
|
+
timeout: Math.min(timeout, 30_000),
|
|
78
|
+
encoding: "utf-8",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
const message = err instanceof Error ? err.stderr || err.message : String(err);
|
|
83
|
+
return {
|
|
84
|
+
stdout: "",
|
|
85
|
+
stderr: `Compilation failed:\n${message}`,
|
|
86
|
+
exitCode: 1,
|
|
87
|
+
timedOut: false,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// Run
|
|
91
|
+
return this.#spawn([binPath], cwd, timeout);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Smart truncation: keeps head (60%) + tail (40%) of output,
|
|
95
|
+
* preserving both initial context and final error messages.
|
|
96
|
+
* Snaps to line boundaries and handles UTF-8 safely.
|
|
97
|
+
*/
|
|
98
|
+
static #smartTruncate(raw, max) {
|
|
99
|
+
if (Buffer.byteLength(raw) <= max)
|
|
100
|
+
return raw;
|
|
101
|
+
const lines = raw.split("\n");
|
|
102
|
+
// Budget: 60% head, 40% tail (errors/results are usually at the end)
|
|
103
|
+
const headBudget = Math.floor(max * 0.6);
|
|
104
|
+
const tailBudget = max - headBudget;
|
|
105
|
+
// Collect head lines
|
|
106
|
+
const headLines = [];
|
|
107
|
+
let headBytes = 0;
|
|
108
|
+
for (const line of lines) {
|
|
109
|
+
const lineBytes = Buffer.byteLength(line) + 1; // +1 for \n
|
|
110
|
+
if (headBytes + lineBytes > headBudget)
|
|
111
|
+
break;
|
|
112
|
+
headLines.push(line);
|
|
113
|
+
headBytes += lineBytes;
|
|
114
|
+
}
|
|
115
|
+
// Collect tail lines (from end)
|
|
116
|
+
const tailLines = [];
|
|
117
|
+
let tailBytes = 0;
|
|
118
|
+
for (let i = lines.length - 1; i >= headLines.length; i--) {
|
|
119
|
+
const lineBytes = Buffer.byteLength(lines[i]) + 1;
|
|
120
|
+
if (tailBytes + lineBytes > tailBudget)
|
|
121
|
+
break;
|
|
122
|
+
tailLines.unshift(lines[i]);
|
|
123
|
+
tailBytes += lineBytes;
|
|
124
|
+
}
|
|
125
|
+
const skippedLines = lines.length - headLines.length - tailLines.length;
|
|
126
|
+
const skippedBytes = Buffer.byteLength(raw) - headBytes - tailBytes;
|
|
127
|
+
const separator = `\n\n... [${skippedLines} lines / ${(skippedBytes / 1024).toFixed(1)}KB truncated — showing first ${headLines.length} + last ${tailLines.length} lines] ...\n\n`;
|
|
128
|
+
return headLines.join("\n") + separator + tailLines.join("\n");
|
|
129
|
+
}
|
|
130
|
+
async #spawn(cmd, cwd, timeout) {
|
|
131
|
+
return new Promise((res) => {
|
|
132
|
+
const proc = spawn(cmd[0], cmd.slice(1), {
|
|
133
|
+
cwd,
|
|
134
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
135
|
+
env: this.#buildSafeEnv(cwd),
|
|
136
|
+
});
|
|
137
|
+
let timedOut = false;
|
|
138
|
+
const timer = setTimeout(() => {
|
|
139
|
+
timedOut = true;
|
|
140
|
+
proc.kill("SIGKILL");
|
|
141
|
+
}, timeout);
|
|
142
|
+
// Collect ALL output in full — smart truncation happens after
|
|
143
|
+
// process exits so we can keep head + tail.
|
|
144
|
+
// OOM is bounded by timeout (default 30s) and maxOutputBytes
|
|
145
|
+
// (used only for truncation threshold, not stream limiting).
|
|
146
|
+
const stdoutChunks = [];
|
|
147
|
+
const stderrChunks = [];
|
|
148
|
+
proc.stdout.on("data", (chunk) => {
|
|
149
|
+
stdoutChunks.push(chunk);
|
|
150
|
+
});
|
|
151
|
+
proc.stderr.on("data", (chunk) => {
|
|
152
|
+
stderrChunks.push(chunk);
|
|
153
|
+
});
|
|
154
|
+
proc.on("close", (exitCode) => {
|
|
155
|
+
clearTimeout(timer);
|
|
156
|
+
const rawStdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
157
|
+
const rawStderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
158
|
+
const max = this.#maxOutputBytes;
|
|
159
|
+
const stdout = _a.#smartTruncate(rawStdout, max);
|
|
160
|
+
const stderr = _a.#smartTruncate(rawStderr, max);
|
|
161
|
+
res({
|
|
162
|
+
stdout,
|
|
163
|
+
stderr,
|
|
164
|
+
exitCode: timedOut ? 1 : (exitCode ?? 1),
|
|
165
|
+
timedOut,
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
proc.on("error", (err) => {
|
|
169
|
+
clearTimeout(timer);
|
|
170
|
+
res({
|
|
171
|
+
stdout: "",
|
|
172
|
+
stderr: err.message,
|
|
173
|
+
exitCode: 1,
|
|
174
|
+
timedOut: false,
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
#buildSafeEnv(tmpDir) {
|
|
180
|
+
const realHome = process.env.HOME ?? process.env.USERPROFILE ?? tmpDir;
|
|
181
|
+
// Pass through auth-related env vars so CLI tools (gh, aws, gcloud, etc.) work
|
|
182
|
+
const passthrough = [
|
|
183
|
+
// GitHub
|
|
184
|
+
"GH_TOKEN",
|
|
185
|
+
"GITHUB_TOKEN",
|
|
186
|
+
"GH_HOST",
|
|
187
|
+
// AWS
|
|
188
|
+
"AWS_ACCESS_KEY_ID",
|
|
189
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
190
|
+
"AWS_SESSION_TOKEN",
|
|
191
|
+
"AWS_REGION",
|
|
192
|
+
"AWS_DEFAULT_REGION",
|
|
193
|
+
"AWS_PROFILE",
|
|
194
|
+
// Google Cloud
|
|
195
|
+
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
196
|
+
"CLOUDSDK_CONFIG",
|
|
197
|
+
// Docker / K8s
|
|
198
|
+
"DOCKER_HOST",
|
|
199
|
+
"KUBECONFIG",
|
|
200
|
+
// Node / npm
|
|
201
|
+
"NPM_TOKEN",
|
|
202
|
+
"NODE_AUTH_TOKEN",
|
|
203
|
+
"npm_config_registry",
|
|
204
|
+
// General
|
|
205
|
+
"HTTP_PROXY",
|
|
206
|
+
"HTTPS_PROXY",
|
|
207
|
+
"NO_PROXY",
|
|
208
|
+
"SSL_CERT_FILE",
|
|
209
|
+
"CURL_CA_BUNDLE",
|
|
210
|
+
// XDG (config paths for gh, gcloud, etc.)
|
|
211
|
+
"XDG_CONFIG_HOME",
|
|
212
|
+
"XDG_DATA_HOME",
|
|
213
|
+
];
|
|
214
|
+
const env = {
|
|
215
|
+
PATH: process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin",
|
|
216
|
+
HOME: realHome,
|
|
217
|
+
TMPDIR: tmpDir,
|
|
218
|
+
LANG: "en_US.UTF-8",
|
|
219
|
+
PYTHONDONTWRITEBYTECODE: "1",
|
|
220
|
+
PYTHONUNBUFFERED: "1",
|
|
221
|
+
NO_COLOR: "1",
|
|
222
|
+
};
|
|
223
|
+
for (const key of passthrough) {
|
|
224
|
+
if (process.env[key]) {
|
|
225
|
+
env[key] = process.env[key];
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return env;
|
|
229
|
+
}
|
|
230
|
+
#wrapWithFileContent(absolutePath, language, code) {
|
|
231
|
+
const escaped = JSON.stringify(absolutePath);
|
|
232
|
+
switch (language) {
|
|
233
|
+
case "javascript":
|
|
234
|
+
case "typescript":
|
|
235
|
+
return `const FILE_CONTENT = require("fs").readFileSync(${escaped}, "utf-8");\n${code}`;
|
|
236
|
+
case "python":
|
|
237
|
+
return `with open(${escaped}, "r") as _f:\n FILE_CONTENT = _f.read()\n${code}`;
|
|
238
|
+
case "shell":
|
|
239
|
+
return `FILE_CONTENT=$(cat ${escaped})\n${code}`;
|
|
240
|
+
case "ruby":
|
|
241
|
+
return `FILE_CONTENT = File.read(${escaped})\n${code}`;
|
|
242
|
+
case "go":
|
|
243
|
+
return `package main\n\nimport (\n\t"fmt"\n\t"os"\n)\n\nfunc main() {\n\tb, _ := os.ReadFile(${escaped})\n\tFILE_CONTENT := string(b)\n\t_ = FILE_CONTENT\n${code}\n}\n`;
|
|
244
|
+
case "rust":
|
|
245
|
+
return `use std::fs;\n\nfn main() {\n let file_content = fs::read_to_string(${escaped}).unwrap();\n${code}\n}\n`;
|
|
246
|
+
case "php":
|
|
247
|
+
return `<?php\n$FILE_CONTENT = file_get_contents(${escaped});\n${code}`;
|
|
248
|
+
case "perl":
|
|
249
|
+
return `open(my $fh, '<', ${escaped}) or die "Cannot open: $!";\nmy $FILE_CONTENT = do { local $/; <$fh> };\nclose($fh);\n${code}`;
|
|
250
|
+
case "r":
|
|
251
|
+
return `FILE_CONTENT <- readLines(${escaped}, warn=FALSE)\nFILE_CONTENT <- paste(FILE_CONTENT, collapse="\\n")\n${code}`;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
_a = PolyglotExecutor;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type Language = "javascript" | "typescript" | "python" | "shell" | "ruby" | "go" | "rust" | "php" | "perl" | "r";
|
|
2
|
+
export interface RuntimeInfo {
|
|
3
|
+
command: string;
|
|
4
|
+
available: boolean;
|
|
5
|
+
version: string;
|
|
6
|
+
preferred: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface RuntimeMap {
|
|
9
|
+
javascript: string;
|
|
10
|
+
typescript: string | null;
|
|
11
|
+
python: string | null;
|
|
12
|
+
shell: string;
|
|
13
|
+
ruby: string | null;
|
|
14
|
+
go: string | null;
|
|
15
|
+
rust: string | null;
|
|
16
|
+
php: string | null;
|
|
17
|
+
perl: string | null;
|
|
18
|
+
r: string | null;
|
|
19
|
+
}
|
|
20
|
+
export declare function detectRuntimes(): RuntimeMap;
|
|
21
|
+
export declare function hasBunRuntime(): boolean;
|
|
22
|
+
export declare function getRuntimeSummary(runtimes: RuntimeMap): string;
|
|
23
|
+
export declare function getAvailableLanguages(runtimes: RuntimeMap): Language[];
|
|
24
|
+
export declare function buildCommand(runtimes: RuntimeMap, language: Language, filePath: string): string[];
|
package/build/runtime.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
function commandExists(cmd) {
|
|
3
|
+
try {
|
|
4
|
+
execSync(`command -v ${cmd} 2>/dev/null`, { stdio: "pipe" });
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function getVersion(cmd) {
|
|
12
|
+
try {
|
|
13
|
+
return execSync(`${cmd} --version 2>/dev/null`, {
|
|
14
|
+
encoding: "utf-8",
|
|
15
|
+
timeout: 5000,
|
|
16
|
+
})
|
|
17
|
+
.trim()
|
|
18
|
+
.split("\n")[0];
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return "unknown";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function detectRuntimes() {
|
|
25
|
+
const hasBun = commandExists("bun");
|
|
26
|
+
return {
|
|
27
|
+
javascript: hasBun ? "bun" : "node",
|
|
28
|
+
typescript: hasBun
|
|
29
|
+
? "bun"
|
|
30
|
+
: commandExists("tsx")
|
|
31
|
+
? "tsx"
|
|
32
|
+
: commandExists("ts-node")
|
|
33
|
+
? "ts-node"
|
|
34
|
+
: null,
|
|
35
|
+
python: commandExists("python3")
|
|
36
|
+
? "python3"
|
|
37
|
+
: commandExists("python")
|
|
38
|
+
? "python"
|
|
39
|
+
: null,
|
|
40
|
+
shell: commandExists("bash") ? "bash" : "sh",
|
|
41
|
+
ruby: commandExists("ruby") ? "ruby" : null,
|
|
42
|
+
go: commandExists("go") ? "go" : null,
|
|
43
|
+
rust: commandExists("rustc") ? "rustc" : null,
|
|
44
|
+
php: commandExists("php") ? "php" : null,
|
|
45
|
+
perl: commandExists("perl") ? "perl" : null,
|
|
46
|
+
r: commandExists("Rscript")
|
|
47
|
+
? "Rscript"
|
|
48
|
+
: commandExists("r")
|
|
49
|
+
? "r"
|
|
50
|
+
: null,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function hasBunRuntime() {
|
|
54
|
+
return commandExists("bun");
|
|
55
|
+
}
|
|
56
|
+
export function getRuntimeSummary(runtimes) {
|
|
57
|
+
const lines = [];
|
|
58
|
+
const bunPreferred = runtimes.javascript === "bun";
|
|
59
|
+
lines.push(` JavaScript: ${runtimes.javascript} (${getVersion(runtimes.javascript)})${bunPreferred ? " ⚡" : ""}`);
|
|
60
|
+
if (runtimes.typescript) {
|
|
61
|
+
lines.push(` TypeScript: ${runtimes.typescript} (${getVersion(runtimes.typescript)})`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
lines.push(` TypeScript: not available (install bun, tsx, or ts-node)`);
|
|
65
|
+
}
|
|
66
|
+
if (runtimes.python) {
|
|
67
|
+
lines.push(` Python: ${runtimes.python} (${getVersion(runtimes.python)})`);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
lines.push(` Python: not available`);
|
|
71
|
+
}
|
|
72
|
+
lines.push(` Shell: ${runtimes.shell} (${getVersion(runtimes.shell)})`);
|
|
73
|
+
// Optional runtimes — only show if available
|
|
74
|
+
if (runtimes.ruby)
|
|
75
|
+
lines.push(` Ruby: ${runtimes.ruby} (${getVersion(runtimes.ruby)})`);
|
|
76
|
+
if (runtimes.go)
|
|
77
|
+
lines.push(` Go: ${runtimes.go} (${getVersion(runtimes.go)})`);
|
|
78
|
+
if (runtimes.rust)
|
|
79
|
+
lines.push(` Rust: ${runtimes.rust} (${getVersion(runtimes.rust)})`);
|
|
80
|
+
if (runtimes.php)
|
|
81
|
+
lines.push(` PHP: ${runtimes.php} (${getVersion(runtimes.php)})`);
|
|
82
|
+
if (runtimes.perl)
|
|
83
|
+
lines.push(` Perl: ${runtimes.perl} (${getVersion(runtimes.perl)})`);
|
|
84
|
+
if (runtimes.r)
|
|
85
|
+
lines.push(` R: ${runtimes.r} (${getVersion(runtimes.r)})`);
|
|
86
|
+
if (!bunPreferred) {
|
|
87
|
+
lines.push("");
|
|
88
|
+
lines.push(" Tip: Install Bun for 3-5x faster JS/TS execution → https://bun.sh");
|
|
89
|
+
}
|
|
90
|
+
return lines.join("\n");
|
|
91
|
+
}
|
|
92
|
+
export function getAvailableLanguages(runtimes) {
|
|
93
|
+
const langs = ["javascript", "shell"];
|
|
94
|
+
if (runtimes.typescript)
|
|
95
|
+
langs.push("typescript");
|
|
96
|
+
if (runtimes.python)
|
|
97
|
+
langs.push("python");
|
|
98
|
+
if (runtimes.ruby)
|
|
99
|
+
langs.push("ruby");
|
|
100
|
+
if (runtimes.go)
|
|
101
|
+
langs.push("go");
|
|
102
|
+
if (runtimes.rust)
|
|
103
|
+
langs.push("rust");
|
|
104
|
+
if (runtimes.php)
|
|
105
|
+
langs.push("php");
|
|
106
|
+
if (runtimes.perl)
|
|
107
|
+
langs.push("perl");
|
|
108
|
+
if (runtimes.r)
|
|
109
|
+
langs.push("r");
|
|
110
|
+
return langs;
|
|
111
|
+
}
|
|
112
|
+
export function buildCommand(runtimes, language, filePath) {
|
|
113
|
+
switch (language) {
|
|
114
|
+
case "javascript":
|
|
115
|
+
return runtimes.javascript === "bun"
|
|
116
|
+
? ["bun", "run", filePath]
|
|
117
|
+
: ["node", filePath];
|
|
118
|
+
case "typescript":
|
|
119
|
+
if (!runtimes.typescript) {
|
|
120
|
+
throw new Error("No TypeScript runtime available. Install one of: bun (recommended), tsx (npm i -g tsx), or ts-node.");
|
|
121
|
+
}
|
|
122
|
+
if (runtimes.typescript === "bun")
|
|
123
|
+
return ["bun", "run", filePath];
|
|
124
|
+
if (runtimes.typescript === "tsx")
|
|
125
|
+
return ["tsx", filePath];
|
|
126
|
+
return ["ts-node", filePath];
|
|
127
|
+
case "python":
|
|
128
|
+
if (!runtimes.python) {
|
|
129
|
+
throw new Error("No Python runtime available. Install python3 or python.");
|
|
130
|
+
}
|
|
131
|
+
return [runtimes.python, filePath];
|
|
132
|
+
case "shell":
|
|
133
|
+
return [runtimes.shell, filePath];
|
|
134
|
+
case "ruby":
|
|
135
|
+
if (!runtimes.ruby) {
|
|
136
|
+
throw new Error("Ruby not available. Install ruby.");
|
|
137
|
+
}
|
|
138
|
+
return [runtimes.ruby, filePath];
|
|
139
|
+
case "go":
|
|
140
|
+
if (!runtimes.go) {
|
|
141
|
+
throw new Error("Go not available. Install go.");
|
|
142
|
+
}
|
|
143
|
+
return ["go", "run", filePath];
|
|
144
|
+
case "rust": {
|
|
145
|
+
if (!runtimes.rust) {
|
|
146
|
+
throw new Error("Rust not available. Install rustc via https://rustup.rs");
|
|
147
|
+
}
|
|
148
|
+
// Rust needs compile + run — handled specially in executor
|
|
149
|
+
return ["__rust_compile_run__", filePath];
|
|
150
|
+
}
|
|
151
|
+
case "php":
|
|
152
|
+
if (!runtimes.php) {
|
|
153
|
+
throw new Error("PHP not available. Install php.");
|
|
154
|
+
}
|
|
155
|
+
return ["php", filePath];
|
|
156
|
+
case "perl":
|
|
157
|
+
if (!runtimes.perl) {
|
|
158
|
+
throw new Error("Perl not available. Install perl.");
|
|
159
|
+
}
|
|
160
|
+
return ["perl", filePath];
|
|
161
|
+
case "r":
|
|
162
|
+
if (!runtimes.r) {
|
|
163
|
+
throw new Error("R not available. Install R / Rscript.");
|
|
164
|
+
}
|
|
165
|
+
return [runtimes.r, filePath];
|
|
166
|
+
}
|
|
167
|
+
}
|