Loading...
Loading...
Create a lang/ plugin that wires any CLI tool or language runtime into gm-cc — adds exec:<id> dispatch, optional LSP diagnostics, and optional prompt context injection. Zero hook configuration required.
npx skill4agent add anentrypoint/plugforge create-lang-plugin<projectDir>/lang/<id>.js'use strict';
module.exports = {
id: 'mytool', // must match filename: lang/mytool.js
exec: {
match: /^exec:mytool/, // regex tested against full "exec:mytool\n<code>" string
run(code, cwd) { // returns string or Promise<string>
// ...
}
},
lsp: { // optional — synchronous only
check(fileContent, cwd) { // returns Diagnostic[] synchronously
// ...
}
},
extensions: ['.ext'], // optional — file extensions lsp.check applies to
context: `=== mytool ===\n...` // optional — string or () => string
};type Diagnostic = { line: number; col: number; severity: 'error'|'warning'; message: string };exec.runexec:mytool\n<code>exec:mytool output:\n\n<result>lsp.checkexecFileSyncspawnSynccontextadditionalContextmatchexec:mytool\n<code>/^exec:mytool/gdlinttscdenorufftool eval <expr>tool -e <code>tool run <file>tool <file>const http = require('http');
function httpPost(port, urlPath, body) {
return new Promise((resolve, reject) => {
const data = JSON.stringify(body);
const req = http.request(
{ hostname: '127.0.0.1', port, path: urlPath, method: 'POST',
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } },
(res) => { let raw = ''; res.on('data', c => raw += c); res.on('end', () => { try { resolve(JSON.parse(raw)); } catch { resolve({ raw }); } }); }
);
req.setTimeout(8000, () => { req.destroy(); reject(new Error('timeout')); });
req.on('error', reject);
req.write(data); req.end();
});
}const fs = require('fs');
const os = require('os');
const path = require('path');
const { execFileSync } = require('child_process');
function runFile(code, cwd) {
const tmp = path.join(os.tmpdir(), `plugin_${Date.now()}.ext`);
fs.writeFileSync(tmp, code);
try {
return execFileSync('mytool', ['run', tmp], { cwd, encoding: 'utf8', timeout: 10000 });
} finally {
try { fs.unlinkSync(tmp); } catch (_) {}
}
}function isSingleExpr(code) {
return !code.trim().includes('\n') && !/\b(func|def|fn |class|import)\b/.test(code);
}const { spawnSync } = require('child_process');
const fs = require('fs');
const os = require('os');
const path = require('path');
function check(fileContent, cwd) {
const tmp = path.join(os.tmpdir(), `lsp_${Math.random().toString(36).slice(2)}.ext`);
try {
fs.writeFileSync(tmp, fileContent);
const r = spawnSync('mytool', ['check', tmp], { encoding: 'utf8', cwd });
const output = r.stdout + r.stderr;
return output.split('\n').reduce((acc, line) => {
const m = line.match(/^.+:(\d+):(\d+):\s+(error|warning):\s+(.+)$/);
if (m) acc.push({ line: parseInt(m[1]), col: parseInt(m[2]), severity: m[3], message: m[4].trim() });
return acc;
}, []);
} catch (_) {
return [];
} finally {
try { fs.unlinkSync(tmp); } catch (_) {}
}
}file:line:col: error: messagefile:line: E001: messageEWJSON.parse(r.stdout).errors.map(...)exec:<id>context: `=== mytool exec: support ===
exec:mytool
<expression or code block>
Runs via <how>. Use for <when>.`lang/<id>.jsid.jsexec:nodejs
const p = require('/abs/path/to/lang/mytool.js');
console.log(p.id, typeof p.exec.run, p.exec.match.toString());exec:mytool
<a simple test expression>exec:mytool output:exec.runexec.runlsp.checkmodule.exports = { ... }exec.runidmatchC:/dev/godot-kit/lang/gdscript.js