- Add full Telegram bot functionality with Z.AI API integration
- Implement 4 tools: Bash, FileEdit, WebSearch, Git
- Add 3 agents: Code Reviewer, Architect, DevOps Engineer
- Add 6 skills for common coding tasks
- Add systemd service file for 24/7 operation
- Add nginx configuration for HTTPS webhook
- Add comprehensive documentation
- Implement WebSocket server for real-time updates
- Add logging system with Winston
- Add environment validation
🤖 zCode CLI X - Agentic coder with Z.AI + Telegram integration
163 lines
6.6 KiB
JavaScript
Executable File
163 lines
6.6 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
import { Command } from 'commander';
|
|
import { SandboxManager } from './index.js';
|
|
import { spawn } from 'child_process';
|
|
import { logForDebugging } from './utils/debug.js';
|
|
import { loadConfig, loadConfigFromString } from './utils/config-loader.js';
|
|
import * as readline from 'readline';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import * as os from 'os';
|
|
/**
|
|
* Get default config path
|
|
*/
|
|
function getDefaultConfigPath() {
|
|
return path.join(os.homedir(), '.srt-settings.json');
|
|
}
|
|
/**
|
|
* Create a minimal default config if no config file exists
|
|
*/
|
|
function getDefaultConfig() {
|
|
return {
|
|
network: {
|
|
allowedDomains: [],
|
|
deniedDomains: [],
|
|
},
|
|
filesystem: {
|
|
denyRead: [],
|
|
allowRead: [],
|
|
allowWrite: [],
|
|
denyWrite: [],
|
|
},
|
|
};
|
|
}
|
|
async function main() {
|
|
const program = new Command();
|
|
program
|
|
.name('srt')
|
|
.description('Run commands in a sandbox with network and filesystem restrictions')
|
|
.version(process.env.npm_package_version || '1.0.0');
|
|
// Default command - run command in sandbox
|
|
program
|
|
.argument('[command...]', 'command to run in the sandbox')
|
|
.option('-d, --debug', 'enable debug logging')
|
|
.option('-s, --settings <path>', 'path to config file (default: ~/.srt-settings.json)')
|
|
.option('-c <command>', 'run command string directly (like sh -c), no escaping applied')
|
|
.option('--control-fd <fd>', 'read config updates from file descriptor (JSON lines protocol)', parseInt)
|
|
.allowUnknownOption()
|
|
.action(async (commandArgs, options) => {
|
|
try {
|
|
// Enable debug logging if requested
|
|
if (options.debug) {
|
|
process.env.DEBUG = 'true';
|
|
}
|
|
// Load config from file
|
|
const configPath = options.settings || getDefaultConfigPath();
|
|
let runtimeConfig = loadConfig(configPath);
|
|
if (!runtimeConfig) {
|
|
logForDebugging(`No config found at ${configPath}, using default config`);
|
|
runtimeConfig = getDefaultConfig();
|
|
}
|
|
// Initialize sandbox with config
|
|
logForDebugging('Initializing sandbox...');
|
|
await SandboxManager.initialize(runtimeConfig);
|
|
// Set up control fd for dynamic config updates if specified
|
|
let controlReader = null;
|
|
if (options.controlFd !== undefined) {
|
|
try {
|
|
const controlStream = fs.createReadStream('', {
|
|
fd: options.controlFd,
|
|
});
|
|
controlReader = readline.createInterface({
|
|
input: controlStream,
|
|
crlfDelay: Infinity,
|
|
});
|
|
controlReader.on('line', line => {
|
|
const newConfig = loadConfigFromString(line);
|
|
if (newConfig) {
|
|
logForDebugging(`Config updated from control fd: ${JSON.stringify(newConfig)}`);
|
|
SandboxManager.updateConfig(newConfig);
|
|
}
|
|
else if (line.trim()) {
|
|
// Only log non-empty lines that failed to parse
|
|
logForDebugging(`Invalid config on control fd (ignored): ${line}`);
|
|
}
|
|
});
|
|
controlReader.on('error', err => {
|
|
logForDebugging(`Control fd error: ${err.message}`);
|
|
});
|
|
logForDebugging(`Listening for config updates on fd ${options.controlFd}`);
|
|
}
|
|
catch (err) {
|
|
logForDebugging(`Failed to open control fd ${options.controlFd}: ${err instanceof Error ? err.message : String(err)}`);
|
|
}
|
|
}
|
|
// Cleanup control reader on exit
|
|
process.on('exit', () => {
|
|
controlReader?.close();
|
|
});
|
|
// Determine command string based on mode
|
|
let command;
|
|
if (options.c) {
|
|
// -c mode: use command string directly, no escaping
|
|
command = options.c;
|
|
logForDebugging(`Command string mode (-c): ${command}`);
|
|
}
|
|
else if (commandArgs.length > 0) {
|
|
// Default mode: simple join
|
|
command = commandArgs.join(' ');
|
|
logForDebugging(`Original command: ${command}`);
|
|
}
|
|
else {
|
|
console.error('Error: No command specified. Use -c <command> or provide command arguments.');
|
|
process.exit(1);
|
|
}
|
|
logForDebugging(JSON.stringify(SandboxManager.getNetworkRestrictionConfig(), null, 2));
|
|
// Wrap the command with sandbox restrictions
|
|
const sandboxedCommand = await SandboxManager.wrapWithSandbox(command);
|
|
// Execute the sandboxed command
|
|
const child = spawn(sandboxedCommand, {
|
|
shell: true,
|
|
stdio: 'inherit',
|
|
});
|
|
// Handle process exit
|
|
child.on('exit', (code, signal) => {
|
|
// Clean up bwrap mount point artifacts before exiting.
|
|
// On Linux, bwrap creates empty files on the host when protecting
|
|
// non-existent deny paths. This removes them.
|
|
SandboxManager.cleanupAfterCommand();
|
|
if (signal) {
|
|
if (signal === 'SIGINT' || signal === 'SIGTERM') {
|
|
process.exit(0);
|
|
}
|
|
else {
|
|
console.error(`Process killed by signal: ${signal}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
process.exit(code ?? 0);
|
|
});
|
|
child.on('error', error => {
|
|
console.error(`Failed to execute command: ${error.message}`);
|
|
process.exit(1);
|
|
});
|
|
// Handle cleanup on interrupt
|
|
process.on('SIGINT', () => {
|
|
child.kill('SIGINT');
|
|
});
|
|
process.on('SIGTERM', () => {
|
|
child.kill('SIGTERM');
|
|
});
|
|
}
|
|
catch (error) {
|
|
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
process.exit(1);
|
|
}
|
|
});
|
|
program.parse();
|
|
}
|
|
main().catch(error => {
|
|
console.error('Fatal error:', error);
|
|
process.exit(1);
|
|
});
|
|
//# sourceMappingURL=cli.js.map
|