feat: Complete zCode CLI X with Telegram bot integration

- 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
This commit is contained in:
admin
2026-05-05 09:01:26 +00:00
Unverified
parent 4a7035dd92
commit 875c7f9b91
24688 changed files with 3224957 additions and 221 deletions

View File

@@ -0,0 +1,11 @@
import { type SandboxRuntimeConfig } from '../sandbox/sandbox-config.js';
/**
* Parse and validate sandbox configuration from a string
* Used for parsing config from control fd (JSON lines protocol)
*/
export declare function loadConfigFromString(content: string): SandboxRuntimeConfig | null;
/**
* Load and validate sandbox configuration from a file
*/
export declare function loadConfig(filePath: string): SandboxRuntimeConfig | null;
//# sourceMappingURL=config-loader.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/utils/config-loader.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,8BAA8B,CAAA;AAErC;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,GACd,oBAAoB,GAAG,IAAI,CAe7B;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAmCxE"}

View File

@@ -0,0 +1,60 @@
import * as fs from 'fs';
import { SandboxRuntimeConfigSchema, } from '../sandbox/sandbox-config.js';
/**
* Parse and validate sandbox configuration from a string
* Used for parsing config from control fd (JSON lines protocol)
*/
export function loadConfigFromString(content) {
if (!content.trim()) {
return null;
}
try {
const parsed = JSON.parse(content);
const result = SandboxRuntimeConfigSchema.safeParse(parsed);
if (!result.success) {
return null;
}
return result.data;
}
catch {
return null;
}
}
/**
* Load and validate sandbox configuration from a file
*/
export function loadConfig(filePath) {
try {
if (!fs.existsSync(filePath)) {
return null;
}
const content = fs.readFileSync(filePath, 'utf-8');
if (content.trim() === '') {
return null;
}
// Parse JSON
const parsed = JSON.parse(content);
// Validate with zod schema
const result = SandboxRuntimeConfigSchema.safeParse(parsed);
if (!result.success) {
console.error(`Invalid configuration in ${filePath}:`);
result.error.issues.forEach(issue => {
const path = issue.path.join('.');
console.error(` - ${path}: ${issue.message}`);
});
return null;
}
return result.data;
}
catch (error) {
// Log parse errors to help users debug invalid config files
if (error instanceof SyntaxError) {
console.error(`Invalid JSON in config file ${filePath}: ${error.message}`);
}
else {
console.error(`Failed to load config from ${filePath}: ${error}`);
}
return null;
}
}
//# sourceMappingURL=config-loader.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../../src/utils/config-loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AACxB,OAAO,EACL,0BAA0B,GAE3B,MAAM,8BAA8B,CAAA;AAErC;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAe;IAEf,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QACpB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAClC,MAAM,MAAM,GAAG,0BAA0B,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;QAC3D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,IAAI,CAAA;QACb,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAA;IACpB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,IAAI,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAA;QACb,CAAC;QACD,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QAClD,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAC1B,OAAO,IAAI,CAAA;QACb,CAAC;QAED,aAAa;QACb,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAElC,2BAA2B;QAC3B,MAAM,MAAM,GAAG,0BAA0B,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;QAE3D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,CAAC,KAAK,CAAC,4BAA4B,QAAQ,GAAG,CAAC,CAAA;YACtD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;gBAClC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;gBACjC,OAAO,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAA;YAChD,CAAC,CAAC,CAAA;YACF,OAAO,IAAI,CAAA;QACb,CAAC;QAED,OAAO,MAAM,CAAC,IAAI,CAAA;IACpB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,4DAA4D;QAC5D,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;YACjC,OAAO,CAAC,KAAK,CAAC,+BAA+B,QAAQ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAA;QAC5E,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,8BAA8B,QAAQ,KAAK,KAAK,EAAE,CAAC,CAAA;QACnE,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC"}

View File

@@ -0,0 +1,7 @@
/**
* Simple debug logging for standalone sandbox
*/
export declare function logForDebugging(message: string, options?: {
level?: 'info' | 'error' | 'warn';
}): void;
//# sourceMappingURL=debug.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../../src/utils/debug.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAA;CAAE,GAC9C,IAAI,CAsBN"}

View File

@@ -0,0 +1,25 @@
/**
* Simple debug logging for standalone sandbox
*/
export function logForDebugging(message, options) {
// Only log if SRT_DEBUG environment variable is set
// Using SRT_DEBUG instead of DEBUG to avoid conflicts with other tools
// (DEBUG is commonly used by Node.js debug libraries and VS Code)
if (!process.env.SRT_DEBUG) {
return;
}
const level = options?.level || 'info';
const prefix = '[SandboxDebug]';
// Always use stderr to avoid corrupting stdout JSON streams
switch (level) {
case 'error':
console.error(`${prefix} ${message}`);
break;
case 'warn':
console.warn(`${prefix} ${message}`);
break;
default:
console.error(`${prefix} ${message}`);
}
}
//# sourceMappingURL=debug.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../../src/utils/debug.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAe,EACf,OAA+C;IAE/C,oDAAoD;IACpD,uEAAuE;IACvE,kEAAkE;IAClE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;QAC3B,OAAM;IACR,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,MAAM,CAAA;IACtC,MAAM,MAAM,GAAG,gBAAgB,CAAA;IAE/B,4DAA4D;IAC5D,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,OAAO;YACV,OAAO,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC,CAAA;YACrC,MAAK;QACP,KAAK,MAAM;YACT,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC,CAAA;YACpC,MAAK;QACP;YACE,OAAO,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC,CAAA;IACzC,CAAC;AACH,CAAC"}

View File

@@ -0,0 +1,15 @@
/**
* Platform detection utilities
*/
export type Platform = 'macos' | 'linux' | 'windows' | 'unknown';
/**
* Get the WSL version (1 or 2+) if running in WSL.
* Returns undefined if not running in WSL.
*/
export declare function getWslVersion(): string | undefined;
/**
* Detect the current platform.
* Note: All Linux including WSL returns 'linux'. Use getWslVersion() to detect WSL1 (unsupported).
*/
export declare function getPlatform(): Platform;
//# sourceMappingURL=platform.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../../src/utils/platform.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAA;AAEhE;;;GAGG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAwBlD;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,QAAQ,CAatC"}

View File

@@ -0,0 +1,49 @@
/**
* Platform detection utilities
*/
import * as fs from 'fs';
/**
* Get the WSL version (1 or 2+) if running in WSL.
* Returns undefined if not running in WSL.
*/
export function getWslVersion() {
if (process.platform !== 'linux') {
return undefined;
}
try {
const procVersion = fs.readFileSync('/proc/version', { encoding: 'utf8' });
// Check for explicit WSL version markers (e.g., "WSL2", "WSL3", etc.)
const wslVersionMatch = procVersion.match(/WSL(\d+)/i);
if (wslVersionMatch && wslVersionMatch[1]) {
return wslVersionMatch[1];
}
// If no explicit WSL version but contains Microsoft, assume WSL1
// This handles the original WSL1 format: "4.4.0-19041-Microsoft"
if (procVersion.toLowerCase().includes('microsoft')) {
return '1';
}
return undefined;
}
catch {
return undefined;
}
}
/**
* Detect the current platform.
* Note: All Linux including WSL returns 'linux'. Use getWslVersion() to detect WSL1 (unsupported).
*/
export function getPlatform() {
switch (process.platform) {
case 'darwin':
return 'macos';
case 'linux':
// WSL2+ is treated as Linux (same sandboxing)
// WSL1 is also returned as 'linux' but will fail isSupportedPlatform check
return 'linux';
case 'win32':
return 'windows';
default:
return 'unknown';
}
}
//# sourceMappingURL=platform.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../../src/utils/platform.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AAIxB;;;GAGG;AACH,MAAM,UAAU,aAAa;IAC3B,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,EAAE,CAAC,YAAY,CAAC,eAAe,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAA;QAE1E,sEAAsE;QACtE,MAAM,eAAe,GAAG,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;QACtD,IAAI,eAAe,IAAI,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1C,OAAO,eAAe,CAAC,CAAC,CAAC,CAAA;QAC3B,CAAC;QAED,iEAAiE;QACjE,iEAAiE;QACjE,IAAI,WAAW,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YACpD,OAAO,GAAG,CAAA;QACZ,CAAC;QAED,OAAO,SAAS,CAAA;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW;IACzB,QAAQ,OAAO,CAAC,QAAQ,EAAE,CAAC;QACzB,KAAK,QAAQ;YACX,OAAO,OAAO,CAAA;QAChB,KAAK,OAAO;YACV,8CAA8C;YAC9C,2EAA2E;YAC3E,OAAO,OAAO,CAAA;QAChB,KAAK,OAAO;YACV,OAAO,SAAS,CAAA;QAClB;YACE,OAAO,SAAS,CAAA;IACpB,CAAC;AACH,CAAC"}

View File

@@ -0,0 +1,22 @@
export interface RipgrepConfig {
command: string;
args?: string[];
/** Override argv[0] when spawning (for multicall binaries that dispatch on argv[0]) */
argv0?: string;
}
/**
* Check if ripgrep (rg) is available synchronously
* Returns true if rg is installed, false otherwise
*/
export declare function hasRipgrepSync(): boolean;
/**
* Execute ripgrep with the given arguments
* @param args Command-line arguments to pass to rg
* @param target Target directory or file to search
* @param abortSignal AbortSignal to cancel the operation
* @param config Ripgrep configuration (command and optional args)
* @returns Array of matching lines (one per line of output)
* @throws Error if ripgrep exits with non-zero status (except exit code 1 which means no matches)
*/
export declare function ripGrep(args: string[], target: string, abortSignal: AbortSignal, config?: RipgrepConfig): Promise<string[]>;
//# sourceMappingURL=ripgrep.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ripgrep.d.ts","sourceRoot":"","sources":["../../src/utils/ripgrep.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,uFAAuF;IACvF,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED;;;;;;;;GAQG;AACH,wBAAsB,OAAO,CAC3B,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,WAAW,EACxB,MAAM,GAAE,aAAiC,GACxC,OAAO,CAAC,MAAM,EAAE,CAAC,CA2BnB"}

View File

@@ -0,0 +1,45 @@
import { spawn } from 'child_process';
import { text } from 'node:stream/consumers';
import { whichSync } from './which.js';
/**
* Check if ripgrep (rg) is available synchronously
* Returns true if rg is installed, false otherwise
*/
export function hasRipgrepSync() {
return whichSync('rg') !== null;
}
/**
* Execute ripgrep with the given arguments
* @param args Command-line arguments to pass to rg
* @param target Target directory or file to search
* @param abortSignal AbortSignal to cancel the operation
* @param config Ripgrep configuration (command and optional args)
* @returns Array of matching lines (one per line of output)
* @throws Error if ripgrep exits with non-zero status (except exit code 1 which means no matches)
*/
export async function ripGrep(args, target, abortSignal, config = { command: 'rg' }) {
const { command, args: commandArgs = [], argv0 } = config;
const child = spawn(command, [...commandArgs, ...args, target], {
argv0,
signal: abortSignal,
timeout: 10000,
windowsHide: true,
});
const [stdout, stderr, code] = await Promise.all([
text(child.stdout),
text(child.stderr),
new Promise((resolve, reject) => {
child.on('close', resolve);
child.on('error', reject);
}),
]);
if (code === 0) {
return stdout.trim().split('\n').filter(Boolean);
}
if (code === 1) {
// Exit code 1 means "no matches found" - this is normal
return [];
}
throw new Error(`ripgrep failed with exit code ${code}: ${stderr}`);
}
//# sourceMappingURL=ripgrep.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ripgrep.js","sourceRoot":"","sources":["../../src/utils/ripgrep.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAStC;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,SAAS,CAAC,IAAI,CAAC,KAAK,IAAI,CAAA;AACjC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,IAAc,EACd,MAAc,EACd,WAAwB,EACxB,SAAwB,EAAE,OAAO,EAAE,IAAI,EAAE;IAEzC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,EAAE,EAAE,KAAK,EAAE,GAAG,MAAM,CAAA;IAEzD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,GAAG,WAAW,EAAE,GAAG,IAAI,EAAE,MAAM,CAAC,EAAE;QAC9D,KAAK;QACL,MAAM,EAAE,WAAW;QACnB,OAAO,EAAE,KAAM;QACf,WAAW,EAAE,IAAI;KAClB,CAAC,CAAA;IAEF,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC/C,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;QAClB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;QAClB,IAAI,OAAO,CAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC7C,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YAC1B,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAC3B,CAAC,CAAC;KACH,CAAC,CAAA;IAEF,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;QACf,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAClD,CAAC;IACD,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;QACf,wDAAwD;QACxD,OAAO,EAAE,CAAA;IACX,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,iCAAiC,IAAI,KAAK,MAAM,EAAE,CAAC,CAAA;AACrE,CAAC"}

View File

@@ -0,0 +1,9 @@
/**
* Find the path to an executable, similar to the `which` command.
* Uses Bun.which when running in Bun, falls back to spawnSync for Node.js.
*
* @param bin - The name of the executable to find
* @returns The full path to the executable, or null if not found
*/
export declare function whichSync(bin: string): string | null;
//# sourceMappingURL=which.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"which.d.ts","sourceRoot":"","sources":["../../src/utils/which.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkBpD"}

View File

@@ -0,0 +1,25 @@
import { spawnSync } from 'node:child_process';
/**
* Find the path to an executable, similar to the `which` command.
* Uses Bun.which when running in Bun, falls back to spawnSync for Node.js.
*
* @param bin - The name of the executable to find
* @returns The full path to the executable, or null if not found
*/
export function whichSync(bin) {
// Check if we're running in Bun
if (typeof globalThis.Bun !== 'undefined') {
return globalThis.Bun.which(bin);
}
// Fallback to Node.js implementation
const result = spawnSync('which', [bin], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
timeout: 1000,
});
if (result.status === 0 && result.stdout) {
return result.stdout.trim();
}
return null;
}
//# sourceMappingURL=which.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"which.js","sourceRoot":"","sources":["../../src/utils/which.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE9C;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,gCAAgC;IAChC,IAAI,OAAO,UAAU,CAAC,GAAG,KAAK,WAAW,EAAE,CAAC;QAC1C,OAAO,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAClC,CAAC;IAED,qCAAqC;IACrC,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE;QACvC,QAAQ,EAAE,MAAM;QAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;QACnC,OAAO,EAAE,IAAI;KACd,CAAC,CAAA;IAEF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QACzC,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAA;IAC7B,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC"}