diff --git a/extension.js b/extension.js new file mode 100644 index 0000000..9ed9e78 --- /dev/null +++ b/extension.js @@ -0,0 +1,3551 @@ +const vscode = require('vscode'); +const https = require('https'); +const http = require('http'); +const os = require('os'); +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); +const { execSync } = require('child_process'); + +let currentAgent = 'alfred'; +let convId = null; +let csrfToken = null; +let sessionCookie = null; +let userProfile = null; +let hmacSecretCache = null; + +// ═══════════════════════════════════════════════════════════════════════════ +// WORKSPACE INTELLIGENCE ENGINE — Deep awareness of project, files, git +// ═══════════════════════════════════════════════════════════════════════════ + +function getWorkspaceRoot() { + const folders = vscode.workspace.workspaceFolders; + return folders && folders.length > 0 ? folders[0].uri.fsPath : os.homedir(); +} + +function safeExec(cmd, cwd, timeout) { + try { + return execSync(cmd, { cwd: cwd || getWorkspaceRoot(), encoding: 'utf8', timeout: timeout || 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim(); + } catch (_) { return ''; } +} + +function getProjectStructure() { + const root = getWorkspaceRoot(); + try { + const tree = safeExec('find . -maxdepth 3 -not -path "./.git/*" -not -path "./node_modules/*" -not -path "./.venv/*" -not -path "./__pycache__/*" -not -path "./vendor/*" -not -path "./dist/*" -not -path "./build/*" | sort | head -200', root); + return tree || ''; + } catch (_) { return ''; } +} + +function detectProjectType() { + const root = getWorkspaceRoot(); + const indicators = []; + const checks = [ + ['package.json', 'Node.js / JavaScript'], + ['tsconfig.json', 'TypeScript'], + ['composer.json', 'PHP / Composer'], + ['requirements.txt', 'Python'], + ['pyproject.toml', 'Python (modern)'], + ['Cargo.toml', 'Rust'], + ['go.mod', 'Go'], + ['pom.xml', 'Java / Maven'], + ['build.gradle', 'Java / Gradle'], + ['Gemfile', 'Ruby'], + ['.htaccess', 'Apache Web Server'], + ['Dockerfile', 'Docker'], + ['docker-compose.yml', 'Docker Compose'], + ['Makefile', 'Make-based build'], + ['CMakeLists.txt', 'C/C++ CMake'], + ['.env', 'Environment config'], + ['webpack.config.js', 'Webpack'], + ['vite.config.ts', 'Vite'], + ['next.config.js', 'Next.js'], + ['nuxt.config.ts', 'Nuxt'], + ['tailwind.config.js', 'Tailwind CSS'], + ]; + for (const [file, label] of checks) { + try { + if (fs.existsSync(path.join(root, file))) indicators.push(label); + } catch (_) {} + } + return indicators; +} + +function getGitInfo() { + const root = getWorkspaceRoot(); + const branch = safeExec('git rev-parse --abbrev-ref HEAD', root); + if (!branch) return null; + const status = safeExec('git status --porcelain | head -30', root); + const lastCommit = safeExec('git log -1 --format="%h %s (%cr)"', root); + const remoteUrl = safeExec('git remote get-url origin', root); + const dirty = safeExec('git diff --stat | tail -1', root); + const ahead = safeExec('git rev-list --count @{u}..HEAD 2>/dev/null || echo 0', root); + const behind = safeExec('git rev-list --count HEAD..@{u} 2>/dev/null || echo 0', root); + return { branch, status, lastCommit, remoteUrl, dirty, ahead, behind }; +} + +function getOpenEditors() { + const editors = []; + for (const group of vscode.window.tabGroups.all) { + for (const tab of group.tabs) { + if (tab.input && tab.input.uri) { + const rel = vscode.workspace.asRelativePath(tab.input.uri, false); + editors.push(rel); + } + } + } + return editors; +} + +function getDiagnosticsSummary() { + const diags = vscode.languages.getDiagnostics(); + let errors = 0, warnings = 0; + const errorFiles = []; + for (const [uri, items] of diags) { + for (const d of items) { + if (d.severity === vscode.DiagnosticSeverity.Error) { + errors++; + const rel = vscode.workspace.asRelativePath(uri, false); + if (!errorFiles.includes(rel)) errorFiles.push(rel); + } else if (d.severity === vscode.DiagnosticSeverity.Warning) { + warnings++; + } + } + } + return { errors, warnings, errorFiles: errorFiles.slice(0, 10) }; +} + +function getTerminalNames() { + return vscode.window.terminals.map(t => t.name); +} + +function getActiveFileDetails() { + const editor = vscode.window.activeTextEditor; + if (!editor) return null; + const doc = editor.document; + const sel = editor.selection; + const result = { + file: vscode.workspace.asRelativePath(doc.uri, false), + fullPath: doc.fileName, + language: doc.languageId, + lineCount: doc.lineCount, + isDirty: doc.isDirty, + cursorLine: sel.active.line + 1, + cursorCol: sel.active.character + 1, + }; + if (!sel.isEmpty) { + result.selectedText = doc.getText(sel).substring(0, 3000); + result.selectionRange = `L${sel.start.line + 1}-L${sel.end.line + 1}`; + } else { + // Include surrounding context (20 lines around cursor) + const startLine = Math.max(0, sel.active.line - 10); + const endLine = Math.min(doc.lineCount - 1, sel.active.line + 10); + const range = new vscode.Range(startLine, 0, endLine, doc.lineAt(endLine).text.length); + result.surroundingCode = doc.getText(range).substring(0, 2000); + result.contextRange = `L${startLine + 1}-L${endLine + 1}`; + } + return result; +} + +function readFileContent(filePath, maxChars) { + try { + const root = getWorkspaceRoot(); + const fullPath = path.isAbsolute(filePath) ? filePath : path.join(root, filePath); + // Security: don't read outside workspace + if (!fullPath.startsWith(root) && !fullPath.startsWith(os.homedir())) return null; + const stat = fs.statSync(fullPath); + if (stat.size > 500000) return `[File too large: ${(stat.size / 1024).toFixed(0)} KB]`; + return fs.readFileSync(fullPath, 'utf8').substring(0, maxChars || 50000); + } catch (e) { return null; } +} + +function searchFilesInWorkspace(pattern) { + const root = getWorkspaceRoot(); + const results = safeExec(`find . -maxdepth 6 -type f -not -path "./.git/*" -not -path "./node_modules/*" -not -path "./.venv/*" -iname "*${pattern.replace(/[^a-zA-Z0-9._-]/g, '')}*" | head -30`, root); + return results ? results.split('\n').filter(Boolean) : []; +} + +function grepInWorkspace(text, filePattern) { + const root = getWorkspaceRoot(); + const safeText = text.replace(/['"\\$`]/g, ''); + const fileArg = filePattern ? ` --include="${filePattern.replace(/[^a-zA-Z0-9.*_-]/g, '')}"` : ''; + const results = safeExec(`grep -rn --color=never${fileArg} -m 50 "${safeText}" . --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=.venv | head -50`, root, 8000); + return results || ''; +} + +// ═══════════════════════════════════════════════════════════════════════════ +// GIT OPERATIONS TOOLKIT — Full git power from chat +// ═══════════════════════════════════════════════════════════════════════════ + +function gitCommit(message) { + const root = getWorkspaceRoot(); + const safeMsg = message.replace(/"/g, '\\"').replace(/\$/g, '\\$').substring(0, 500); + const addResult = safeExec('git add -A', root); + const commitResult = safeExec(`git commit -m "${safeMsg}" 2>&1`, root, 15000); + return commitResult || 'Nothing to commit'; +} + +function gitDiff(filePath, staged) { + const root = getWorkspaceRoot(); + const stageFlag = staged ? '--staged ' : ''; + const fileArg = filePath ? ` -- "${filePath.replace(/"/g, '')}"` : ''; + return safeExec(`git diff ${stageFlag}--stat${fileArg}`, root, 10000) + '\n' + + safeExec(`git diff ${stageFlag}${fileArg}`, root, 10000).substring(0, 30000); +} + +function gitLog(count, filePath) { + const root = getWorkspaceRoot(); + const n = Math.min(Math.max(parseInt(count) || 10, 1), 100); + const fileArg = filePath ? ` -- "${filePath.replace(/"/g, '')}"` : ''; + return safeExec(`git log --oneline --decorate -n ${n}${fileArg}`, root, 10000); +} + +function gitBlame(filePath, startLine, endLine) { + const root = getWorkspaceRoot(); + const safePath = filePath.replace(/"/g, ''); + const lineRange = (startLine && endLine) ? `-L ${parseInt(startLine)},${parseInt(endLine)} ` : ''; + return safeExec(`git blame ${lineRange}"${safePath}" 2>&1`, root, 10000).substring(0, 20000); +} + +function gitStash(action, message) { + const root = getWorkspaceRoot(); + if (action === 'list') return safeExec('git stash list', root); + if (action === 'pop') return safeExec('git stash pop 2>&1', root, 10000); + if (action === 'drop') return safeExec('git stash drop 2>&1', root); + const safeMsg = message ? ` -m "${message.replace(/"/g, '\\"').substring(0, 200)}"` : ''; + return safeExec(`git stash push${safeMsg} 2>&1`, root, 10000); +} + +function gitBranch(action, branchName) { + const root = getWorkspaceRoot(); + const safeName = (branchName || '').replace(/[^a-zA-Z0-9/_.\-]/g, '').substring(0, 100); + if (action === 'list') return safeExec('git branch -a', root); + if (action === 'create' && safeName) return safeExec(`git checkout -b "${safeName}" 2>&1`, root); + if (action === 'switch' && safeName) return safeExec(`git checkout "${safeName}" 2>&1`, root); + if (action === 'delete' && safeName) return safeExec(`git branch -d "${safeName}" 2>&1`, root); + return 'Invalid branch operation'; +} + +// ═══════════════════════════════════════════════════════════════════════════ +// SYSTEM & PROCESS TOOLS — Server awareness, ports, processes, env +// ═══════════════════════════════════════════════════════════════════════════ + +function getSystemInfo() { + return { + hostname: os.hostname(), + platform: os.platform(), + arch: os.arch(), + cpus: os.cpus().length, + totalMem: (os.totalmem() / 1073741824).toFixed(1) + ' GB', + freeMem: (os.freemem() / 1073741824).toFixed(1) + ' GB', + uptime: (os.uptime() / 3600).toFixed(1) + ' hours', + nodeVersion: process.version, + user: os.userInfo().username, + homeDir: os.homedir(), + loadAvg: os.loadavg().map(l => l.toFixed(2)).join(', '), + }; +} + +function getRunningPorts() { + return safeExec("ss -tlnp 2>/dev/null | grep LISTEN | awk '{print $4, $6}' | head -40", os.homedir(), 8000); +} + +function getPm2Services() { + return safeExec('pm2 jlist 2>/dev/null', os.homedir(), 10000); +} + +function getDiskUsage() { + return safeExec("df -h / /home 2>/dev/null | tail -n +2", os.homedir()); +} + +function getEnvironmentVars(filter) { + const safeFilter = (filter || '').replace(/[^a-zA-Z0-9_*]/g, ''); + if (safeFilter) { + return safeExec(`env | grep -i "${safeFilter}" | sort | head -50`, os.homedir()); + } + return safeExec('env | grep -v "^LS_COLORS\\|^SSH_\\|^LESSOPEN\\|^XDG_" | sort | head -80', os.homedir()); +} + +// ═══════════════════════════════════════════════════════════════════════════ +// DATA FORMAT UTILITIES — JSON, base64, hashing, encoding +// ═══════════════════════════════════════════════════════════════════════════ + +function formatJson(text) { + try { return JSON.stringify(JSON.parse(text), null, 2); } + catch (e) { return 'Invalid JSON: ' + e.message; } +} + +function computeHash(text, algorithm) { + const algo = ['sha256', 'sha512', 'md5', 'sha1'].includes(algorithm) ? algorithm : 'sha256'; + return crypto.createHash(algo).update(text).digest('hex'); +} + +function base64Encode(text) { return Buffer.from(text).toString('base64'); } +function base64Decode(text) { + try { return Buffer.from(text, 'base64').toString('utf8'); } + catch (e) { return 'Invalid base64: ' + e.message; } +} + +function urlEncode(text) { return encodeURIComponent(text); } +function urlDecode(text) { + try { return decodeURIComponent(text); } + catch (e) { return 'Invalid URL encoding: ' + e.message; } +} + +function generateUuid() { return crypto.randomUUID(); } + +function countLines(filePath) { + try { + const root = getWorkspaceRoot(); + const fullPath = path.isAbsolute(filePath) ? filePath : path.join(root, filePath); + const content = fs.readFileSync(fullPath, 'utf8'); + const lines = content.split('\n').length; + const chars = content.length; + const words = content.split(/\s+/).filter(Boolean).length; + const blanks = content.split('\n').filter(l => !l.trim()).length; + return { lines, chars, words, blanks, codeLines: lines - blanks }; + } catch (e) { return { error: e.message }; } +} + +// ═══════════════════════════════════════════════════════════════════════════ +// ADVANCED FILE OPERATIONS — Diff, multi-edit, bulk search-replace +// ═══════════════════════════════════════════════════════════════════════════ + +function diffFiles(fileA, fileB) { + const root = getWorkspaceRoot(); + const pathA = path.isAbsolute(fileA) ? fileA : path.join(root, fileA); + const pathB = path.isAbsolute(fileB) ? fileB : path.join(root, fileB); + return safeExec(`diff -u "${pathA}" "${pathB}" 2>&1`, root, 10000).substring(0, 30000); +} + +function findReplace(filePath, search, replace, isRegex) { + try { + const root = getWorkspaceRoot(); + const fullPath = path.isAbsolute(filePath) ? filePath : path.join(root, filePath); + let content = fs.readFileSync(fullPath, 'utf8'); + let count = 0; + if (isRegex) { + const re = new RegExp(search, 'g'); + content = content.replace(re, (...args) => { count++; return replace; }); + } else { + while (content.includes(search)) { + content = content.replace(search, replace); + count++; + if (count > 10000) break; + } + } + if (count > 0) fs.writeFileSync(fullPath, content, 'utf8'); + return { count, filePath }; + } catch (e) { return { error: e.message }; } +} + +function getRecentFiles(count) { + const root = getWorkspaceRoot(); + const n = Math.min(parseInt(count) || 20, 50); + return safeExec(`find . -maxdepth 4 -type f -not -path "./.git/*" -not -path "./node_modules/*" -printf "%T@ %p\\n" 2>/dev/null | sort -rn | head -${n} | awk '{print $2}'`, root, 8000); +} + +function getFileSizes(pattern) { + const root = getWorkspaceRoot(); + const safePattern = (pattern || '*').replace(/[^a-zA-Z0-9.*_\-/]/g, ''); + return safeExec(`find . -maxdepth 4 -type f -name "${safePattern}" -not -path "./.git/*" -not -path "./node_modules/*" -exec ls -lhS {} + 2>/dev/null | head -30`, root, 8000); +} + +// ═══════════════════════════════════════════════════════════════════════════ +// TEST RUNNER — Detect and run project tests +// ═══════════════════════════════════════════════════════════════════════════ + +function detectTestFramework() { + const root = getWorkspaceRoot(); + const frameworks = []; + try { + const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); + const deps = { ...(pkg.devDependencies || {}), ...(pkg.dependencies || {}) }; + if (deps.jest || deps['@jest/core']) frameworks.push({ name: 'jest', cmd: 'npx jest --verbose' }); + if (deps.mocha) frameworks.push({ name: 'mocha', cmd: 'npx mocha' }); + if (deps.vitest) frameworks.push({ name: 'vitest', cmd: 'npx vitest run' }); + if (deps.ava) frameworks.push({ name: 'ava', cmd: 'npx ava' }); + if (pkg.scripts && pkg.scripts.test && pkg.scripts.test !== 'echo "Error: no test specified" && exit 1') { + frameworks.push({ name: 'npm-test', cmd: 'npm test' }); + } + } catch (_) {} + if (fs.existsSync(path.join(root, 'pytest.ini')) || fs.existsSync(path.join(root, 'pyproject.toml'))) { + frameworks.push({ name: 'pytest', cmd: 'python -m pytest -v' }); + } + if (fs.existsSync(path.join(root, 'phpunit.xml')) || fs.existsSync(path.join(root, 'phpunit.xml.dist'))) { + frameworks.push({ name: 'phpunit', cmd: 'vendor/bin/phpunit' }); + } + if (fs.existsSync(path.join(root, 'Cargo.toml'))) { + frameworks.push({ name: 'cargo-test', cmd: 'cargo test' }); + } + if (fs.existsSync(path.join(root, 'go.mod'))) { + frameworks.push({ name: 'go-test', cmd: 'go test ./...' }); + } + return frameworks; +} + +function runTests(framework, testFile) { + const root = getWorkspaceRoot(); + const fw = detectTestFramework().find(f => !framework || f.name === framework) || detectTestFramework()[0]; + if (!fw) return 'No test framework detected'; + const fileArg = testFile ? ` "${testFile.replace(/"/g, '')}"` : ''; + return safeExec(`${fw.cmd}${fileArg} 2>&1`, root, 60000).substring(0, 30000); +} + +// ═══════════════════════════════════════════════════════════════════════════ +// ENHANCED CONTEXT ENGINE — Import graph, complexity, dependency audit +// ═══════════════════════════════════════════════════════════════════════════ + +function getImportGraph(filePath) { + try { + const root = getWorkspaceRoot(); + const fullPath = path.isAbsolute(filePath) ? filePath : path.join(root, filePath); + const content = fs.readFileSync(fullPath, 'utf8'); + const imports = []; + // JS/TS imports + const jsImports = content.matchAll(/(?:import\s+.*?from\s+['"](.+?)['"]|require\s*\(\s*['"](.+?)['"]\s*\))/g); + for (const m of jsImports) imports.push(m[1] || m[2]); + // Python imports + const pyImports = content.matchAll(/(?:from\s+(\S+)\s+import|import\s+(\S+))/g); + for (const m of pyImports) imports.push(m[1] || m[2]); + // PHP includes + const phpIncludes = content.matchAll(/(?:require|include)(?:_once)?\s*[\(]?\s*['"](.+?)['"]/g); + for (const m of phpIncludes) imports.push(m[1]); + // Go imports + const goImports = content.matchAll(/import\s+(?:\(\s*([\s\S]*?)\)|"(.+?)")/g); + for (const m of goImports) { + if (m[1]) { + for (const line of m[1].split('\n')) { + const gm = line.match(/["'](.+?)["']/); + if (gm) imports.push(gm[1]); + } + } else if (m[2]) imports.push(m[2]); + } + // Rust use + const rustUse = content.matchAll(/use\s+(\S+)/g); + for (const m of rustUse) imports.push(m[1].replace(/;$/, '')); + return { file: filePath, imports: [...new Set(imports)] }; + } catch (e) { return { file: filePath, imports: [], error: e.message }; } +} + +function analyzeComplexity(filePath) { + try { + const root = getWorkspaceRoot(); + const fullPath = path.isAbsolute(filePath) ? filePath : path.join(root, filePath); + const content = fs.readFileSync(fullPath, 'utf8'); + const lines = content.split('\n'); + const totalLines = lines.length; + const blankLines = lines.filter(l => !l.trim()).length; + const commentLines = lines.filter(l => { + const t = l.trim(); + return t.startsWith('//') || t.startsWith('#') || t.startsWith('*') || t.startsWith('/*') || t.startsWith('