gaunt-sloth-assistant
Version:
[](https://github.com/Galvanized-Pukeko/gaunt-sloth-assistant/actions/workflows/unit-tests.yml) [ {
const toolInstance = tool(fn, config);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
toolInstance.gthDevType = gthDevType;
return toolInstance;
}
// Schema definitions
const RunTestsArgsSchema = z.object({});
const RunLintArgsSchema = z.object({});
const RunBuildArgsSchema = z.object({});
const RunSingleTestArgsSchema = z.object({
testPath: z.string().describe('Relative path to the test file to run'),
});
const TEST_PATH_PLACEHOLDER = '${testPath}';
export default class GthDevToolkit extends BaseToolkit {
tools;
commands;
constructor(commands = {}) {
super();
this.commands = commands;
this.tools = this.createTools();
}
/**
* Get tools filtered by operation type
*/
getFilteredTools(allowedOperations) {
return this.tools.filter((tool) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const toolType = tool.gthDevType;
return allowedOperations.includes(toolType);
});
}
/**
* Validate test path to prevent security issues
*/
validateTestPath(testPath) {
// Check for absolute paths
if (path.isAbsolute(testPath)) {
throw new Error('Absolute paths are not allowed for test files');
}
// Check for directory traversal attempts
if (testPath.includes('..') || testPath.includes('\\..\\') || testPath.includes('/../')) {
throw new Error('Directory traversal attempts are not allowed');
}
// Check for pipe attempts and other shell injection
if (testPath.includes('|') ||
testPath.includes('&') ||
testPath.includes(';') ||
testPath.includes('`')) {
throw new Error('Shell injection attempts are not allowed');
}
// Check for null bytes
if (testPath.includes('\0')) {
throw new Error('Null bytes are not allowed in test path');
}
// Normalize the path to remove any redundant separators
const normalizedPath = path.normalize(testPath);
// Double-check after normalization
if (normalizedPath.includes('..')) {
throw new Error('Directory traversal attempts are not allowed');
}
return normalizedPath;
}
/**
* Build the command for running a single test file
*/
buildSingleTestCommand(testPath) {
if (this.commands.run_single_test) {
if (this.commands.run_single_test.includes(TEST_PATH_PLACEHOLDER)) {
// Interpolate if placeholder is available
return this.commands.run_single_test.replace(TEST_PATH_PLACEHOLDER, testPath);
}
else {
// Concatenate if no placeholder
return `${this.commands.run_single_test} ${testPath}`;
}
}
else {
throw new Error('No test command configured');
}
}
async executeCommand(command, toolName) {
displayInfo(`\n🔧 Executing ${toolName}: ${command}`);
return new Promise((resolve, reject) => {
const child = spawn(command, {
shell: true,
});
let output = '';
// Capture output if available (when stdio is not 'inherit')
if (child.stdout) {
child.stdout.on('data', (data) => {
const chunk = data.toString();
stdout.write(chunk);
output += chunk;
});
}
if (child.stderr) {
child.stderr.on('data', (data) => {
const chunk = data.toString();
stdout.write(chunk);
output += chunk;
});
}
child.on('close', (code) => {
if (code === 0) {
resolve(`Executing '${command}'...\n\n` +
`<COMMAND_OUTPUT>\n` +
output +
`</COMMAND_OUTPUT>\n` +
`\n\nCommand '${command}' completed successfully`);
}
else {
resolve(`Executing '${command}'...\n\n` +
`<COMMAND_OUTPUT>\n` +
output +
`</COMMAND_OUTPUT>\n` +
`\n\nCommand '${command}' exited with code ${code}`);
}
});
child.on('error', (error) => {
const errorMsg = `Failed to execute command '${command}': ${error.message}`;
displayError(errorMsg);
reject(new Error(errorMsg));
});
});
}
createTools() {
const tools = [];
if (this.commands.run_tests) {
tools.push(createGthTool(async (_args) => {
return await this.executeCommand(this.commands.run_tests, 'run_tests');
}, {
name: 'run_tests',
description: 'Execute the test suite for this project. Runs the configured test command and returns the output.' +
`\nThe configured command is [${this.commands.run_tests}].`,
schema: RunTestsArgsSchema,
}, 'execute'));
}
if (this.commands.run_single_test) {
tools.push(createGthTool(async (args) => {
const validatedPath = this.validateTestPath(args.testPath);
const command = this.buildSingleTestCommand(validatedPath);
return await this.executeCommand(command, 'run_single_test');
}, {
name: 'run_single_test',
description: 'Execute a single test file. Runs the configured test command with the specified test file path. ' +
'The test path must be relative and cannot contain directory traversal attempts or shell injection. ' +
`\nThe base command is [${this.commands.run_single_test}].`,
schema: RunSingleTestArgsSchema,
}, 'execute'));
}
if (this.commands.run_lint) {
tools.push(createGthTool(async (_args) => {
return await this.executeCommand(this.commands.run_lint, 'run_lint');
}, {
name: 'run_lint',
description: 'Run the linter on the project code. Executes the configured lint command and returns any linting errors or warnings.' +
`\nThe configured command is [${this.commands.run_lint}].`,
schema: RunLintArgsSchema,
}, 'execute'));
}
if (this.commands.run_build) {
tools.push(createGthTool(async (_args) => {
return await this.executeCommand(this.commands.run_build, 'run_build');
}, {
name: 'run_build',
description: 'Build the project. Executes the configured build command and returns the build output.' +
`\nThe configured command is [${this.commands.run_build}].`,
schema: RunBuildArgsSchema,
}, 'execute'));
}
return tools;
}
}
//# sourceMappingURL=GthDevToolkit.js.map