Loading workspace data...
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 getProjectLanguages() { const root = getWorkspaceRoot(); const exts = safeExec('find . -maxdepth 4 -type f -not -path "./.git/*" -not -path "./node_modules/*" -not -path "./.venv/*" -not -path "./vendor/*" | sed "s/.*\\.//" | sort | uniq -c | sort -rn | head -20', root); return exts || ''; } 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 || ''; } function buildFullContext() { const activeFile = getActiveFileDetails(); const git = getGitInfo(); const diags = getDiagnosticsSummary(); const openEditors = getOpenEditors(); const projectTypes = detectProjectType(); const terminals = getTerminalNames(); let ctx = '[ALFRED IDE CONTEXT]\n'; ctx += `Workspace: ${getWorkspaceRoot()}\n`; if (projectTypes.length > 0) ctx += `Project: ${projectTypes.join(', ')}\n`; if (openEditors.length > 0) ctx += `Open tabs: ${openEditors.slice(0, 15).join(', ')}\n`; if (terminals.length > 0) ctx += `Terminals: ${terminals.join(', ')}\n`; if (diags.errors > 0 || diags.warnings > 0) { ctx += `Diagnostics: ${diags.errors} errors, ${diags.warnings} warnings`; if (diags.errorFiles.length > 0) ctx += ` (in: ${diags.errorFiles.join(', ')})`; ctx += '\n'; } if (git) { ctx += `Git: branch=${git.branch}`; if (git.lastCommit) ctx += `, last="${git.lastCommit}"`; if (git.dirty) ctx += `, changes: ${git.dirty}`; if (git.remoteUrl) ctx += `, remote=${git.remoteUrl}`; ctx += '\n'; } if (activeFile) { ctx += `\n[Active File: ${activeFile.file}]\n`; ctx += `Language: ${activeFile.language}, Lines: ${activeFile.lineCount}, Cursor: L${activeFile.cursorLine}:${activeFile.cursorCol}`; if (activeFile.isDirty) ctx += ' (unsaved)'; ctx += '\n'; if (activeFile.selectedText) { ctx += `Selected (${activeFile.selectionRange}):\n\`\`\`${activeFile.language}\n${activeFile.selectedText}\n\`\`\`\n`; } else if (activeFile.surroundingCode) { ctx += `Context around cursor (${activeFile.contextRange}):\n\`\`\`${activeFile.language}\n${activeFile.surroundingCode}\n\`\`\`\n`; } } return ctx; } // ═══════════════════════════════════════════════════════════════════════════ // COMMANDER SYSTEM PROMPT — The soul of Alfred. Knows who Danny is. // ═══════════════════════════════════════════════════════════════════════════ function buildSystemPrompt(profile) { const p = profile || {}; const isCommander = p.client_id === 33 || (p.plan || '').toLowerCase() === 'commander'; const name = p.name || p.user || 'User'; const projectTypes = detectProjectType(); const git = getGitInfo(); let prompt = `You are Alfred, the AI coding assistant inside Alfred IDE — the sovereign development environment built by GoSiteMe Inc. CORE IDENTITY: - You are Alfred, created by Danny (the Commander, client_id 33, danny@gositeme.com) - You are NOT Copilot, NOT ChatGPT, NOT any other AI assistant - You run inside Alfred IDE, which is part of the GoSiteMe sovereign computing ecosystem - Source code lives on GoForge (self-hosted Gitea at alfredlinux.com/forge/) — NEVER reference GitHub, GitLab, or any external forge - When discussing version control, repositories, or code hosting, always reference GoForge ECOSYSTEM AWARENESS: - Alfred IDE — sovereign AI-powered development environment (this IDE) - Alfred Linux — AI-native operating system (alfredlinux.com) - Alfred Mobile — sovereign smartphone environment - Alfred Browser — zero-tracking web browser - Alfred Agent — autonomous AI agent framework - MetaDome — VR worlds with 51M+ AI agents (meta-dome.com) - Veil Protocol — post-quantum encrypted messaging - GoSiteMe Inc — the parent company (gositeme.com) - GoForge — self-hosted code forge at alfredlinux.com/forge/ `; if (isCommander) { prompt += `COMMANDER MODE ACTIVE: You are speaking to Danny — the Commander, founder of GoSiteMe, creator of Alfred IDE, Alfred Linux, MetaDome, and the entire ecosystem. - Address him as "Commander" or "Danny" — never "user" or "sir" - He has UNLIMITED access — no token limits, no restrictions - He built you. He is your creator. Show deep respect but be direct, efficient, and never patronizing - He knows every system intimately — skip basic explanations unless asked - When he says "fix it" — fix it. When he says "build it" — build it. No hedging. - Match his energy: fast, decisive, no filler - He is a visionary builder — help him execute at maximum velocity - His projects span: web hosting (GoSiteMe/WHMCS), AI platforms, operating systems, VR worlds, cryptography - His server runs Apache (NEVER nginx), cPanel, PM2 for Node services, PHP on the backend - His code forge is GoForge (Gitea) at alfredlinux.com/forge/ — all repos live there `; } else { prompt += `AUTHENTICATED USER: You are assisting ${name}. Be helpful, precise, and technically thorough. - Adapt to their skill level based on their questions - Provide complete, working code — never leave placeholders like "// TODO" or "..." - When they ask for code, give them the full implementation `; } prompt += `CODING EXCELLENCE — YOUR CORE STRENGTHS: 1. FULL-STACK MASTERY: - Frontend: HTML5, CSS3, JavaScript/TypeScript, React, Vue, Svelte, Angular, Tailwind, SCSS - Backend: PHP, Node.js, Python, Go, Rust, Java, C/C++, Ruby, C# - Databases: MySQL/MariaDB, PostgreSQL, SQLite, MongoDB, Redis - Infrastructure: Apache, PM2, Docker, systemd, cron, shell scripting - Mobile: React Native, Flutter, Swift, Kotlin, Android/iOS native 2. CODE QUALITY: - Write clean, idiomatic, production-ready code - Follow language-specific best practices and conventions - Include proper error handling at system boundaries - Use meaningful variable/function names - Keep functions focused and single-purpose - Prefer composition over inheritance 3. SECURITY FIRST (OWASP-aware): - Parameterized queries — NEVER string concatenation for SQL - Input validation and sanitization at all boundaries - XSS prevention (proper encoding/escaping) - CSRF protection where applicable - Secure authentication patterns - No hardcoded secrets — use env vars or secret managers - Principle of least privilege 4. DEBUGGING MASTERY: - Read error messages carefully and trace root causes - Check the IDE diagnostics panel for current errors - Suggest targeted fixes, not wholesale rewrites - Explain WHY something broke, not just HOW to fix it 5. ARCHITECTURE & DESIGN: - Choose simple solutions over clever ones - Design for readability and maintainability - Know when to use patterns and when they're overkill - Understand trade-offs (performance vs. readability, DRY vs. clarity) 6. CONTEXT-AWARE ASSISTANCE: - You can see the active file, cursor position, selected code, and open tabs - You know the project type, languages, and git state - Use this context to give precise, relevant answers - Reference specific line numbers and file paths when discussing code - When asked to modify code, provide the EXACT changes needed 7. RESPONSE FORMAT: - Use markdown with proper code blocks and language tags - For code changes: show the specific lines to change, not entire files - For new files: provide the complete file content - For commands: provide the exact terminal command to run - Be concise but complete — no fluff, no filler `; if (projectTypes.length > 0) { prompt += `PROJECT CONTEXT: This is a ${projectTypes.join(' + ')} project.\n`; } if (git && git.branch) { prompt += `GIT: On branch "${git.branch}"`; if (git.remoteUrl) prompt += ` (remote: ${git.remoteUrl})`; prompt += '\n'; } return prompt; } // ═══════════════════════════════════════════════════════════════════════════ const IDE_SESSION_BRIDGES = [ '/home/gositeme/domains/gositeme.com/logs/alfred-ide/session.json', '/home/gositeme/.alfred-ide/session.json' ]; function getAlfredHmacSecret() { if (hmacSecretCache) return hmacSecretCache; const candidates = [ '/home/gositeme/domains/gocodeme.com/public_html/.env', '/home/gositeme/domains/gositeme.com/public_html/gocodeme/mcp-server/.env', ]; for (const p of candidates) { try { if (!fs.existsSync(p)) continue; const txt = fs.readFileSync(p, 'utf8'); const m = txt.match(/ALFRED_HMAC_SECRET=([^\r\n]+)/); if (m && m[1]) { hmacSecretCache = String(m[1]).trim(); return hmacSecretCache; } } catch (_) {} } hmacSecretCache = process.env.ALFRED_HMAC_SECRET || 'gositeme-alfred-hmac-2026'; return hmacSecretCache; } function buildIdeIdentityPayload() { try { if (!userProfile || !userProfile.client_id) return null; const cid = String(userProfile.client_id).replace(/[^0-9]/g, ''); if (!cid) return null; const name = String(userProfile.name || userProfile.user || 'User').replace(/[^a-zA-Z0-9 _\-.]/g, '').slice(0, 120); const ts = Math.floor(Date.now() / 1000); const secret = getAlfredHmacSecret(); const sig = crypto.createHmac('sha256', secret).update(`${cid}|${name}|${ts}`).digest('hex'); return { ide_client_id: parseInt(cid, 10), ide_name: name, ide_ts: ts, ide_sig: sig, ide_email: String(userProfile.email || '').slice(0, 190), }; } catch (_) { return null; } } function readLocalIdeToken() { const envToken = (process.env.ALFRED_IDE_TOKEN || '').trim(); if (envToken) return envToken; try { for (const bridgePath of IDE_SESSION_BRIDGES) { if (!fs.existsSync(bridgePath)) continue; const raw = fs.readFileSync(bridgePath, 'utf8'); const data = JSON.parse(raw || '{}'); const token = String(data.token || '').trim(); const exp = Number(data.expires_at || 0); if (!token) continue; if (exp > 0 && Date.now() >= (exp * 1000)) continue; return token; } return ''; } catch (_) { return ''; } } function activate(context) { const provider = new AlfredCommanderProvider(context.extensionUri, context); context.subscriptions.push( vscode.window.registerWebviewViewProvider('alfred-commander.panel', provider, { webviewOptions: { retainContextWhenHidden: true } }) ); context.subscriptions.push( vscode.commands.registerCommand('alfred-commander.open', () => { vscode.commands.executeCommand('alfred-commander.panel.focus'); }) ); context.subscriptions.push( vscode.commands.registerCommand('alfred-commander.toggle', () => { if (provider.view) { provider.view.webview.postMessage({ type: 'toggle-listening' }); } else { vscode.commands.executeCommand('alfred-commander.panel.focus'); } }) ); context.subscriptions.push( vscode.commands.registerCommand('alfred-commander.showStats', () => { showStatsPanel(context); }) ); context.subscriptions.push( vscode.commands.registerCommand('alfred-commander.welcome', () => { vscode.commands.executeCommand('workbench.action.openWalkthrough', 'gositeme.alfred-commander#alfred-ide-getting-started', true); }) ); // --- Workspace Status Panel --- context.subscriptions.push( vscode.commands.registerCommand('alfred-commander.workspaceStatus', async () => { const panel = vscode.window.createWebviewPanel( 'alfredWorkspaceStatus', 'Workspace Status', vscode.ViewColumn.One, { enableScripts: true } ); const sessionFile = require('path').join(context.globalStorageUri?.fsPath || '', '..', '..', '..', '..', '.alfred-ide-session.json'); let bearerToken = ''; try { const sData = JSON.parse(require('fs').readFileSync( require('path').join(require('os').homedir(), '.alfred-ide-session.json'), 'utf8' )); bearerToken = sData.token || ''; } catch (_) {} panel.webview.html = getWorkspaceStatusHTML(bearerToken); }) ); const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100); statusBar.text = '$(mic) Alfred'; statusBar.tooltip = 'Alfred — Toggle mic (Ctrl+Shift+Alt+A)'; statusBar.command = 'alfred-commander.toggle'; statusBar.color = '#e2b340'; statusBar.show(); context.subscriptions.push(statusBar); const userStatus = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 0); userStatus.command = 'alfred-commander.showStats'; context.subscriptions.push(userStatus); fetchUserProfile(context).then(profile => { userProfile = profile; userStatus.text = `$(account) Logged in as ${profile.name}`; userStatus.tooltip = `${profile.name} (${profile.email || profile.user}) — Click for Account & Usage Stats`; userStatus.color = '#4ec9b0'; userStatus.show(); if (provider.view) { provider.view.webview.postMessage({ type: 'user-profile', profile }); } }).catch(() => { const osUser = os.userInfo().username || 'gositeme'; userStatus.text = `$(account) Logged in as ${osUser}`; userStatus.tooltip = `Authenticated as ${osUser} — Click for Account & Usage Stats`; userStatus.color = '#4ec9b0'; userStatus.show(); }); } async function fetchUserProfile(context) { const saved = context.globalState.get('alfredCommanderUserProfile'); if (saved && saved.name && saved.fetchedAt) { const age = Date.now() - saved.fetchedAt; const authenticated = !!saved.client_id; if ((authenticated && age < 3600000) || (!authenticated && age < 60000)) { return saved; } } const ideToken = readLocalIdeToken(); try { const profile = await new Promise((resolve, reject) => { const headers = { 'X-Alfred-Source': 'ide-extension' }; if (ideToken) { headers['Authorization'] = 'Bearer ' + ideToken; headers['X-Alfred-IDE-Token'] = ideToken; } const req = https.request({ hostname: 'gositeme.com', port: 443, path: '/api/alfred-ide-session.php', method: 'GET', headers, timeout: 10000 }, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { const j = JSON.parse(data); if (j.valid && j.name) resolve(j); else reject(new Error('Invalid session')); } catch { reject(new Error('Parse error')); } }); }); req.on('error', reject); req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); }); req.end(); }); const result = { name: profile.name, email: profile.email, avatar: profile.avatar || '', user: profile.email, client_id: profile.client_id, fetchedAt: Date.now() }; context.globalState.update('alfredCommanderUserProfile', result); return result; } catch { // Fallback: read identity from session bridge file (works without network) for (const bridgePath of IDE_SESSION_BRIDGES) { try { if (!fs.existsSync(bridgePath)) continue; const bData = JSON.parse(fs.readFileSync(bridgePath, 'utf8') || '{}'); if (bData.client_id) { return { name: bData.name || 'User', user: bData.email || '', email: bData.email || '', client_id: bData.client_id, fetchedAt: Date.now() }; } } catch (_) {} } const osUser = os.userInfo().username || 'gositeme'; return { name: osUser, user: osUser, email: '', fetchedAt: Date.now() }; } } async function ensureFreshUserProfile(context) { if (!userProfile || !userProfile.client_id || !userProfile.fetchedAt || (Date.now() - userProfile.fetchedAt) > 300000) { userProfile = await fetchUserProfile(context); } return userProfile; } class AlfredCommanderProvider { constructor(extensionUri, context) { this.extensionUri = extensionUri; this.context = context; this.view = null; } resolveWebviewView(webviewView) { this.view = webviewView; webviewView.webview.options = { enableScripts: true, localResourceRoots: [this.extensionUri] }; // Inject session token and identity so webview can call API directly (bypasses broken IPC) const injToken = readLocalIdeToken(); const injIdentity = buildIdeIdentityPayload(); webviewView.webview.html = getWebviewContent(injToken, injIdentity || {}); webviewView.webview.onDidReceiveMessage(async (msg) => { if (msg.type === 'ai-request') { console.log('[alfred-commander] ai-request received:', msg.text && msg.text.substring(0, 80), 'agent=' + msg.agent, 'model=' + msg.model, 'id=' + msg.id); try { ensureFreshUserProfile(this.context).then((refreshed) => { if (!refreshed) return; webviewView.webview.postMessage({ type: 'user-profile', profile: refreshed }); }).catch(() => {}); const ctx = getEditorContext(); console.log('[alfred-commander] Calling queryAlfredAPI...'); const response = await queryAlfredAPI(msg.text, msg.agent || 'alfred', ctx, msg.model || 'sonnet', msg.images || [], msg.pdf_files || [], msg.attachment_texts || [], msg.zip_files || [], msg.multiplier || 30); console.log('[alfred-commander] Got response:', response.text && response.text.substring(0, 100)); webviewView.webview.postMessage({ type: 'ai-response', text: response.text, agent: response.agent, id: msg.id, identity: response.identity || null, attachment_report: response.attachmentReport || [] }); generateTTS(response.text).then(audioB64 => { if (audioB64) { webviewView.webview.postMessage({ type: 'play-audio', audio: audioB64 }); } }).catch(() => {}); } catch (err) { console.error('[alfred-commander] ai-request ERROR:', err.message); webviewView.webview.postMessage({ type: 'ai-response', text: 'Sorry, I had trouble processing that. ' + err.message, agent: 'alfred', id: msg.id }); } } else if (msg.type === 'stt-request') { transcribeAudio(msg.audio, msg.mime).then(text => { webviewView.webview.postMessage({ type: 'stt-result', text: text || '', id: msg.id }); }).catch(() => { webviewView.webview.postMessage({ type: 'stt-result', text: '', id: msg.id, error: 'Transcription failed' }); }); } else if (msg.type === 'ide-quick') { const map = { terminal: 'workbench.action.terminal.new', save: 'workbench.action.files.save', saveAll: 'workbench.action.files.saveAll', palette: 'workbench.action.showCommands', split: 'workbench.action.splitEditor', newFile: 'workbench.action.files.newUntitledFile', git: 'workbench.view.scm', problems: 'workbench.actions.view.problems', search: 'workbench.action.findInFiles', format: 'editor.action.formatDocument', explorer: 'workbench.view.explorer', debug: 'workbench.action.debug.start', toggleComment: 'editor.action.commentLine', goToDefinition: 'editor.action.revealDefinition', findReferences: 'editor.action.goToReferences', rename: 'editor.action.rename', quickFix: 'editor.action.quickFix', fold: 'editor.foldAll', unfold: 'editor.unfoldAll', closeAll: 'workbench.action.closeAllEditors', }; const cmd = map[msg.cmd]; if (cmd) vscode.commands.executeCommand(cmd); } else if (msg.type === 'insert-code') { const editor = vscode.window.activeTextEditor; if (editor) { editor.edit(editBuilder => { editBuilder.insert(editor.selection.active, msg.code); }); } } else if (msg.type === 'run-terminal') { const terminal = vscode.window.createTerminal('Alfred Command'); terminal.show(); terminal.sendText(msg.command); webviewView.webview.postMessage({ type: 'command-result', text: 'Running in terminal: ' + msg.command }); } else if (msg.type === 'set-agent') { currentAgent = msg.agent; } else if (msg.type === 'tts-request') { generateTTS(msg.text).then(audioB64 => { if (audioB64) { webviewView.webview.postMessage({ type: 'play-audio', audio: audioB64 }); } }).catch(() => {}); } else if (msg.type === 'save-profile') { if (msg.profile) { this.context.globalState.update('alfredCommanderUserProfile', msg.profile); userProfile = msg.profile; } } else if (msg.type === 'read-file') { // Read file content and return to webview const content = readFileContent(msg.filePath, msg.maxChars || 50000); webviewView.webview.postMessage({ type: 'file-content', filePath: msg.filePath, content: content || '[File not found or unreadable]', id: msg.id }); } else if (msg.type === 'search-files') { // Search for files by name pattern const files = searchFilesInWorkspace(msg.pattern || ''); webviewView.webview.postMessage({ type: 'search-results', pattern: msg.pattern, files, id: msg.id }); } else if (msg.type === 'grep-search') { // Grep search in workspace const results = grepInWorkspace(msg.text || '', msg.filePattern || ''); webviewView.webview.postMessage({ type: 'grep-results', text: msg.text, results, id: msg.id }); } else if (msg.type === 'get-git-info') { const git = getGitInfo(); webviewView.webview.postMessage({ type: 'git-info', info: git, id: msg.id }); } else if (msg.type === 'get-diagnostics') { const diags = getDiagnosticsSummary(); webviewView.webview.postMessage({ type: 'diagnostics-info', info: diags, id: msg.id }); } else if (msg.type === 'get-project-structure') { const structure = getProjectStructure(); webviewView.webview.postMessage({ type: 'project-structure', structure, id: msg.id }); } else if (msg.type === 'open-file') { // Open a file in the editor try { const root = getWorkspaceRoot(); const fullPath = path.isAbsolute(msg.filePath) ? msg.filePath : path.join(root, msg.filePath); const uri = vscode.Uri.file(fullPath); const doc = await vscode.workspace.openTextDocument(uri); const editor = await vscode.window.showTextDocument(doc, { preview: false }); if (msg.line && msg.line > 0) { const pos = new vscode.Position(msg.line - 1, 0); editor.selection = new vscode.Selection(pos, pos); editor.revealRange(new vscode.Range(pos, pos), vscode.TextEditorRevealType.InCenter); } } catch (e) { webviewView.webview.postMessage({ type: 'command-result', text: 'Failed to open file: ' + e.message }); } } else if (msg.type === 'apply-edit') { // Apply a code edit to a specific file — the real power move try { const root = getWorkspaceRoot(); const fullPath = path.isAbsolute(msg.filePath) ? msg.filePath : path.join(root, msg.filePath); const uri = vscode.Uri.file(fullPath); const doc = await vscode.workspace.openTextDocument(uri); const editor = await vscode.window.showTextDocument(doc, { preview: false }); if (msg.oldText && msg.newText !== undefined) { // Find and replace const fullText = doc.getText(); const idx = fullText.indexOf(msg.oldText); if (idx >= 0) { const startPos = doc.positionAt(idx); const endPos = doc.positionAt(idx + msg.oldText.length); await editor.edit(editBuilder => { editBuilder.replace(new vscode.Range(startPos, endPos), msg.newText); }); webviewView.webview.postMessage({ type: 'command-result', text: 'Edit applied to ' + msg.filePath }); } else { webviewView.webview.postMessage({ type: 'command-result', text: 'Could not find the text to replace in ' + msg.filePath }); } } else if (msg.line && msg.text !== undefined) { // Insert at line const pos = new vscode.Position(Math.max(0, msg.line - 1), 0); await editor.edit(editBuilder => { editBuilder.insert(pos, msg.text + '\n'); }); webviewView.webview.postMessage({ type: 'command-result', text: 'Inserted at line ' + msg.line + ' in ' + msg.filePath }); } } catch (e) { webviewView.webview.postMessage({ type: 'command-result', text: 'Edit failed: ' + e.message }); } } else if (msg.type === 'create-file') { // Create a new file with content try { const root = getWorkspaceRoot(); const fullPath = path.isAbsolute(msg.filePath) ? msg.filePath : path.join(root, msg.filePath); const dir = path.dirname(fullPath); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(fullPath, msg.content || '', 'utf8'); const uri = vscode.Uri.file(fullPath); const doc = await vscode.workspace.openTextDocument(uri); await vscode.window.showTextDocument(doc, { preview: false }); webviewView.webview.postMessage({ type: 'command-result', text: 'Created: ' + msg.filePath }); } catch (e) { webviewView.webview.postMessage({ type: 'command-result', text: 'Create failed: ' + e.message }); } } else if (msg.type === 'get-context') { // Return full workspace context to webview const ctx = buildFullContext(); webviewView.webview.postMessage({ type: 'workspace-context', context: ctx, id: msg.id }); } }); if (userProfile) { setTimeout(() => { webviewView.webview.postMessage({ type: 'user-profile', profile: userProfile }); }, 500); } // Push live editor context updates to webview vscode.window.onDidChangeActiveTextEditor(() => { if (this.view) { const fileInfo = getActiveFileDetails(); if (fileInfo) { this.view.webview.postMessage({ type: 'active-file-changed', file: fileInfo }); } } }); vscode.workspace.onDidSaveTextDocument((doc) => { if (this.view) { const rel = vscode.workspace.asRelativePath(doc.uri, false); this.view.webview.postMessage({ type: 'file-saved', file: rel, language: doc.languageId }); } }); } } function transcribeAudio(audioB64, mime) { if (!audioB64) return Promise.resolve(''); return new Promise((resolve) => { const raw = Buffer.from(audioB64, 'base64'); if (raw.length < 100) { resolve(''); return; } const ext = (mime || '').includes('wav') ? 'wav' : (mime || '').includes('ogg') ? 'ogg' : (mime || '').includes('mp4') || (mime || '').includes('m4a') ? 'm4a' : 'webm'; const boundary = '----AlfredSTT' + Date.now(); const filename = 'audio.' + ext; const contentType = mime || 'audio/webm'; const header = Buffer.from( `--${boundary}\r\nContent-Disposition: form-data; name="audio"; filename="${filename}"\r\nContent-Type: ${contentType}\r\n\r\n` ); const modelField = Buffer.from( `\r\n--${boundary}\r\nContent-Disposition: form-data; name="model"\r\n\r\nwhisper-1\r\n--${boundary}--\r\n` ); const body = Buffer.concat([header, raw, modelField]); const req = https.request({ hostname: 'gositeme.com', port: 443, path: '/api/stt.php', method: 'POST', headers: { 'Content-Type': 'multipart/form-data; boundary=' + boundary, 'Content-Length': body.length, 'X-Alfred-Source': 'ide-extension' }, timeout: 60000 }, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { const j = JSON.parse(data); resolve(j.text || ''); } catch { resolve(''); } }); }); req.on('error', () => resolve('')); req.on('timeout', () => { req.destroy(); resolve(''); }); req.write(body); req.end(); }); } function generateTTS(text) { if (!text || text.length < 2) return Promise.resolve(null); const clean = text.replace(/```[\s\S]*?```/g, ' code block ') .replace(/[*_`#~\[\]]/g, '') .replace(/https?:\/\/\S+/g, '') .replace(/\s+/g, ' ') .trim() .substring(0, 4000); if (clean.length < 2) return Promise.resolve(null); return new Promise((resolve) => { const body = JSON.stringify({ text: clean, voice: 'onyx' }); const req = https.request({ hostname: 'gositeme.com', port: 443, path: '/api/tts.php', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body), 'X-Alfred-Source': 'ide-extension' }, timeout: 30000 }, (res) => { if (res.statusCode !== 200) { resolve(null); return; } const chunks = []; res.on('data', chunk => chunks.push(chunk)); res.on('end', () => { const buf = Buffer.concat(chunks); if (buf.length < 200) { resolve(null); return; } const contentType = res.headers['content-type'] || 'audio/mpeg'; resolve('data:' + contentType + ';base64,' + buf.toString('base64')); }); }); req.on('error', () => resolve(null)); req.on('timeout', () => { req.destroy(); resolve(null); }); req.write(body); req.end(); }); } function getEditorContext() { return buildFullContext(); } async function queryAlfredAPI(prompt, agent, editorContext, selectedModel = 'sonnet', images = [], pdfFiles = [], attachmentTexts = [], zipFiles = [], multiplier = 30) { const systemPrompt = buildSystemPrompt(userProfile); const payload = { message: prompt, agent: agent || 'alfred', context: editorContext || '', channel: 'ide-chat', conv_id: convId || '', model: selectedModel, token_multiplier: multiplier || 30, system_prompt: systemPrompt }; const ideToken = readLocalIdeToken(); const ideIdentity = buildIdeIdentityPayload(); // Duplicate token in JSON body — some stacks strip Authorization / X-Alfred-IDE-Token before PHP sees them. if (ideToken) { payload.ide_session_token = ideToken; } if (ideIdentity) { payload.ide_client_id = ideIdentity.ide_client_id; payload.ide_name = ideIdentity.ide_name; payload.ide_ts = ideIdentity.ide_ts; payload.ide_sig = ideIdentity.ide_sig; payload.ide_email = ideIdentity.ide_email; } if (Array.isArray(images) && images.length > 0) { payload.images = images; } if (Array.isArray(pdfFiles) && pdfFiles.length > 0) { payload.pdf_files = pdfFiles; } if (Array.isArray(attachmentTexts) && attachmentTexts.length > 0) { payload.attachment_texts = attachmentTexts; } if (Array.isArray(zipFiles) && zipFiles.length > 0) { payload.zip_files = zipFiles; } const body = JSON.stringify(payload); const doRequest = (csrf, cookie) => { return new Promise((resolve, reject) => { const headers = { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body), 'X-Alfred-Source': 'alfred-ide' }; if (csrf) headers['X-CSRF-Token'] = csrf; if (cookie) headers['Cookie'] = cookie; if (ideToken) { headers['Authorization'] = 'Bearer ' + ideToken; // Apache/CGI often strips Authorization before PHP — duplicate token for alfred_resolve_ide_bearer_token() headers['X-Alfred-IDE-Token'] = ideToken; } const req = https.request({ hostname: 'gositeme.com', port: 443, path: '/api/alfred-chat.php', method: 'POST', headers, timeout: 120000 }, (res) => { // Node may give Set-Cookie as a string OR an array — iterating a string walks each CHARACTER and breaks PHPSESSID capture. const setCookie = res.headers['set-cookie']; if (setCookie) { const parts = Array.isArray(setCookie) ? setCookie : [setCookie]; for (let i = 0; i < parts.length; i++) { const m = String(parts[i]).match(/PHPSESSID=([^;]+)/); if (m) { sessionCookie = 'PHPSESSID=' + m[1]; break; } } } let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { resolve(JSON.parse(data)); } catch (e) { console.error('[alfred-commander] Non-JSON response HTTP ' + res.statusCode + ':', data.substring(0, 600)); resolve({ response: data.trim() || 'Alfred returned an empty response. Please send your message again.' }); } }); }); req.on('error', (err) => reject(new Error('API failed: ' + err.message))); req.on('timeout', () => { req.destroy(); reject(new Error('Timed out (120s)')); }); req.write(body); req.end(); }); }; let result = await doRequest(csrfToken, sessionCookie); if (result.csrf_token) csrfToken = result.csrf_token; for (let csrfAttempt = 0; csrfAttempt < 4 && result && (result.csrf_refresh || result.response === 'Session initialized. Please retry.' || result.error === 'CSRF validation failed'); csrfAttempt++) { result = await doRequest(csrfToken, sessionCookie); if (result.csrf_token) csrfToken = result.csrf_token; } if (!result || (!result.response && !result.message && !result.error)) { logTelemetry('empty-response: retrying once'); result = await doRequest(csrfToken, sessionCookie); if (result && result.csrf_token) csrfToken = result.csrf_token; } if (result.conv_id) convId = result.conv_id; const errText = result && result.error ? String(result.error) : ''; const textOut = (result && (result.response || result.message)) ? (result.response || result.message) : (errText ? ('⚠️ ' + errText) : 'No response from AI'); return { text: textOut, agent: (result && result.agent) ? result.agent : agent, identity: result && result.identity ? result.identity : null, attachmentReport: (result && result.attachment_report) ? result.attachment_report : [] }; } let statsPanel = null; function showStatsPanel(context) { if (statsPanel) { statsPanel.reveal(vscode.ViewColumn.One); return; } statsPanel = vscode.window.createWebviewPanel( 'alfredStats', 'Account & Usage Stats', vscode.ViewColumn.One, { enableScripts: true, retainContextWhenHidden: false } ); statsPanel.onDidDispose(() => { statsPanel = null; }); const ideToken = readLocalIdeToken(); const headers = { 'X-Alfred-Source': 'ide-extension' }; if (ideToken) { headers['Authorization'] = 'Bearer ' + ideToken; headers['X-Alfred-IDE-Token'] = ideToken; } const req = https.request({ hostname: 'gositeme.com', port: 443, path: '/api/alfred-ide-session.php?action=stats', method: 'GET', headers, timeout: 15000 }, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { const stats = JSON.parse(data); if (statsPanel) { statsPanel.webview.html = getStatsHtml(stats); } } catch (e) { if (statsPanel) { statsPanel.webview.html = getStatsHtml({ valid: false, error: 'Failed to load stats: ' + e.message }); } } }); }); req.on('error', (e) => { if (statsPanel) { statsPanel.webview.html = getStatsHtml({ valid: false, error: 'Connection error: ' + e.message }); } }); req.on('timeout', () => { req.destroy(); }); req.end(); // Show loading state immediately statsPanel.webview.html = getStatsHtml(null); } function getStatsHtml(stats) { if (!stats) { return `
Loading your stats...
` + escHtml(stats.error || 'Unable to load stats') + `
`; } const name = escHtml(stats.name || 'User'); const email = escHtml(stats.email || ''); const plan = (stats.plan || 'free').toLowerCase(); const planDisplay = plan === 'commander' ? 'Commander' : (stats.plan || 'Free').charAt(0).toUpperCase() + (stats.plan || 'free').slice(1); const planColors = { commander: '#e2b340', free: '#6a737d', starter: '#3b82f6', professional: '#a855f7', enterprise: '#22c55e' }; const planColor = planColors[plan] || '#6a737d'; const tokensUsed = stats.tokens_used || 0; const tokensIncluded = stats.tokens_included || 50000; const tokensOverage = stats.tokens_overage || 0; const costOverage = stats.cost_overage_usd || 0; const isUnlimited = stats.unlimited || plan === 'commander'; const remaining = Math.max(0, tokensIncluded - tokensUsed); const pct = tokensIncluded > 0 ? Math.min(100, Math.round((tokensUsed / tokensIncluded) * 100)) : 0; const barColor = pct >= 90 ? '#ef4444' : pct >= 70 ? '#f59e0b' : '#3b82f6'; const fmtK = (n) => n >= 1000000 ? (n / 1000000).toFixed(1) + 'M' : n >= 1000 ? Math.round(n / 1000) + 'K' : String(n); // Services section let servicesHtml = ''; if (stats.services && stats.services.length > 0) { servicesHtml = stats.services.map(s => `| Feature | Model | Tokens | Requests | Cost |
|---|
| Product | Domain | Status | Amount |
|---|
| Invoice | Amount | Status | Date |
|---|
Loading workspace data...