From d32f46dc59abcd3f1e61f624dc7c95a55d925dd1 Mon Sep 17 00:00:00 2001 From: Alfred Date: Tue, 7 Apr 2026 12:11:42 -0400 Subject: [PATCH] v1.1.0: Full workspace intelligence engine, Commander identity system, code block actions (copy/insert/run), rich context injection, GoForge-only, purged GitHub refs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Workspace Intelligence: project detection (20+ types), file tree, git info, diagnostics, open editors, terminal names - Commander System Prompt: knows Danny (client_id 33), ecosystem awareness, coding excellence directives - Rich Context: every API call now includes active file, cursor pos, selected code, project type, git branch, diagnostics - Code Block Actions: Copy, Insert at cursor, Run in terminal — buttons on every code block response - New IDE Quick Actions: Git, Problems, Search, Format (added to existing Terminal, Save, Commands, Split, New) - File Operations: read-file, search-files, grep-search, open-file, apply-edit, create-file handlers - Live Context Push: editor changes and file saves pushed to webview in real-time - Commander Greeting: personalized startup message when Danny logs in - GoForge Only: repository + homepage point to alfredlinux.com/forge, removed GitHub Copilot refs from extensions.json - 2093 → 2686 lines --- extension.js | 631 ++++++++++++++++++++++++++++++++++++++++++++++-- extensions.json | 4 +- package.json | 5 + 3 files changed, 618 insertions(+), 22 deletions(-) diff --git a/extension.js b/extension.js index d127b1f..f2e4b1f 100644 --- a/extension.js +++ b/extension.js @@ -3,7 +3,9 @@ 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; @@ -12,6 +14,336 @@ 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' @@ -292,6 +624,20 @@ class AlfredCommanderProvider { 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); @@ -320,6 +666,95 @@ class AlfredCommanderProvider { 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 }); } }); @@ -328,6 +763,23 @@ class AlfredCommanderProvider { 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 }); + } + }); } } @@ -425,27 +877,17 @@ function generateTTS(text) { } function getEditorContext() { - const editor = vscode.window.activeTextEditor; - if (!editor) return ''; - const doc = editor.document; - const sel = editor.selection; - let ctx = `[IDE Context] File: ${doc.fileName}, Language: ${doc.languageId}, Lines: ${doc.lineCount}`; - if (!sel.isEmpty) { - const selected = doc.getText(sel).substring(0, 500); - ctx += `, Selected code:\n\`\`\`${doc.languageId}\n${selected}\n\`\`\``; - } else { - const line = doc.lineAt(sel.active.line); - ctx += `, Current line ${sel.active.line + 1}: ${line.text.substring(0, 200)}`; - } - return ctx; + 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 + token_multiplier: multiplier || 30, + system_prompt: systemPrompt }; const ideToken = readLocalIdeToken(); const ideIdentity = buildIdeIdentityPayload(); @@ -869,6 +1311,16 @@ function getWebviewContent(injectedToken, injectedIdentity) { .ide-quick-label { font-size: 9px; color: var(--vscode-descriptionForeground); text-transform: uppercase; letter-spacing: 0.5px; margin-right: 4px; flex-shrink: 0; } .ide-q-btn { font-size: 10px; padding: 5px 10px; border-radius: 6px; border: 1px solid var(--vscode-input-border, #30363d); background: var(--vscode-input-background, #161b22); color: var(--vscode-foreground); cursor: pointer; white-space: nowrap; } .ide-q-btn:hover { border-color: #e2b340; color: #e2b340; } + .code-block-wrap { margin: 6px 0; border: 1px solid var(--vscode-panel-border, #30363d); border-radius: 6px; overflow: hidden; } + .code-block-header { display: flex; align-items: center; gap: 4px; padding: 4px 8px; background: rgba(255,255,255,0.03); border-bottom: 1px solid var(--vscode-panel-border, #30363d); } + .code-lang { font-size: 9px; color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px; flex: 1; } + .cb-btn { font-size: 9px; padding: 2px 8px; border-radius: 4px; border: 1px solid var(--vscode-input-border, #30363d); background: transparent; cursor: pointer; font-family: inherit; } + .cb-copy { color: #60a5fa; } + .cb-copy:hover { background: rgba(96,165,250,0.15); border-color: #60a5fa; } + .cb-insert { color: #22c55e; } + .cb-insert:hover { background: rgba(34,197,94,0.15); border-color: #22c55e; } + .cb-run { color: #e2b340; } + .cb-run:hover { background: rgba(226,179,64,0.15); border-color: #e2b340; } @@ -950,11 +1402,15 @@ function getWebviewContent(injectedToken, injectedIdentity) { + + + +
-
+
Alfred
-
Ready. Claude Sonnet 4 connected. Speak or type.
+
Initializing workspace intelligence...
@@ -1616,8 +2072,21 @@ function scheduleHandsFreeRecord() { // ── Message formatting ───────────────────────────────────────────────────── function formatResponse(text) { + let blockId = 0; return text - .replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g, '
$2
') + .replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g, function(match, lang, code) { + blockId++; + const escapedCode = code.replace(/&/g, '&').replace(//g, '>'); + const safeLang = (lang || 'text').replace(/[^a-zA-Z0-9]/g, ''); + return '
' + + '
' + safeLang + '' + + '' + + '' + + ((['sh','bash','zsh','shell','cmd','powershell','terminal'].includes(safeLang)) ? + '' : '') + + '
' + + '
' + escapedCode + '
'; + }) .replace(/\`([^\`]+)\`/g, '$1') .replace(/\\*\\*([^*]+)\\*\\*/g, '$1') .replace(/\\n/g, '
'); @@ -1724,10 +2193,13 @@ function processInput(text) { // Direct browser-to-API chat (primary path — no extension host IPC needed) async function alfredDirectChat(reqId, text, agent, model, images, pdfs, texts, zips, multiplier) { try { + // Request workspace context from extension host for richer payload + vscode.postMessage({ type: 'get-context', id: reqId }); + const payload = { message: text, agent: agent || 'alfred', model: model || 'sonnet', token_multiplier: multiplier || 30, - channel: 'ide-chat', context: '', conv_id: '' + channel: 'ide-chat', context: window.__lastWorkspaceContext || '', conv_id: '' }; // Include auth token injected from extension host if (window.__alfredToken) payload.ide_session_token = window.__alfredToken; @@ -1864,6 +2336,54 @@ window.addEventListener('message', (event) => { else if (msg.type === 'user-profile') { applyIdentity(msg.profile || {}); } + else if (msg.type === 'workspace-context') { + // Cache latest workspace context for direct API calls + window.__lastWorkspaceContext = msg.context || ''; + } + else if (msg.type === 'active-file-changed') { + // Update status with current file info + if (msg.file && msg.file.file) { + const statusHint = msg.file.file + ' (' + msg.file.language + ', L' + msg.file.cursorLine + ')'; + logTelemetry('active-file: ' + statusHint); + } + } + else if (msg.type === 'file-saved') { + logTelemetry('saved: ' + (msg.file || 'unknown')); + } + else if (msg.type === 'file-content') { + // File content received — add as system message + if (msg.content) { + addMessage('system', 'File: ' + (msg.filePath || '') + '\\n' + msg.content.substring(0, 2000)); + } + } + else if (msg.type === 'search-results') { + if (msg.files && msg.files.length > 0) { + addMessage('system', 'Found ' + msg.files.length + ' files matching "' + (msg.pattern || '') + '":\\n' + msg.files.join('\\n')); + } else { + addMessage('system', 'No files found matching "' + (msg.pattern || '') + '"'); + } + } + else if (msg.type === 'grep-results') { + if (msg.results) { + addMessage('system', 'Grep results for "' + (msg.text || '') + '":\\n' + msg.results.substring(0, 3000)); + } + } + else if (msg.type === 'git-info') { + if (msg.info) { + const g = msg.info; + addMessage('system', 'Git: branch=' + g.branch + ', last commit: ' + g.lastCommit + (g.dirty ? ', changes: ' + g.dirty : '') + (g.remoteUrl ? ', remote: ' + g.remoteUrl : '')); + } + } + else if (msg.type === 'diagnostics-info') { + if (msg.info) { + addMessage('system', 'Diagnostics: ' + msg.info.errors + ' errors, ' + msg.info.warnings + ' warnings' + (msg.info.errorFiles.length ? ' in: ' + msg.info.errorFiles.join(', ') : '')); + } + } + else if (msg.type === 'project-structure') { + if (msg.structure) { + addMessage('system', 'Project structure:\\n' + msg.structure.substring(0, 3000)); + } + } }); // ── Event listeners ──────────────────────────────────────────────────────── @@ -1926,7 +2446,7 @@ if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { bindSend(); }); } (function bindIdeQuick() { - [['acIdeTerminal','terminal'], ['acIdeSave','save'], ['acIdeSaveAll','saveAll'], ['acIdePalette','palette'], ['acIdeSplit','split'], ['acIdeNew','newFile']].forEach(([id, cmd]) => { + [['acIdeTerminal','terminal'], ['acIdeSave','save'], ['acIdeSaveAll','saveAll'], ['acIdePalette','palette'], ['acIdeSplit','split'], ['acIdeNew','newFile'], ['acIdeGit','git'], ['acIdeProblems','problems'], ['acIdeSearch','search'], ['acIdeFormat','format']].forEach(([id, cmd]) => { const el = document.getElementById(id); if (el) el.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); vscode.postMessage({ type: 'ide-quick', cmd }); }); }); @@ -1956,6 +2476,79 @@ window.addEventListener('unhandledrejection', (ev) => { logTelemetry('promise-error: ' + msg); } catch (_) {} }); + +// ── Code block action buttons ──────────────────────────────────────────── + +function copyCodeBlock(btn) { + try { + const wrap = btn.closest('.code-block-wrap'); + const codeEl = wrap.querySelector('code[data-code]'); + const code = decodeURIComponent(escape(atob(codeEl.getAttribute('data-code')))); + navigator.clipboard.writeText(code).then(() => { + btn.textContent = 'Copied!'; + setTimeout(() => { btn.textContent = 'Copy'; }, 1500); + }).catch(() => { + // Fallback for no clipboard API + const ta = document.createElement('textarea'); + ta.value = code; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); + btn.textContent = 'Copied!'; + setTimeout(() => { btn.textContent = 'Copy'; }, 1500); + }); + } catch(e) { setStatus('Copy failed: ' + e.message); } +} + +function insertCodeBlock(btn) { + try { + const wrap = btn.closest('.code-block-wrap'); + const codeEl = wrap.querySelector('code[data-code]'); + const code = decodeURIComponent(escape(atob(codeEl.getAttribute('data-code')))); + vscode.postMessage({ type: 'insert-code', code: code }); + btn.textContent = 'Inserted!'; + setTimeout(() => { btn.textContent = 'Insert'; }, 1500); + } catch(e) { setStatus('Insert failed: ' + e.message); } +} + +function runCodeBlock(btn) { + try { + const wrap = btn.closest('.code-block-wrap'); + const codeEl = wrap.querySelector('code[data-code]'); + const code = decodeURIComponent(escape(atob(codeEl.getAttribute('data-code')))); + vscode.postMessage({ type: 'run-terminal', command: code.trim() }); + btn.textContent = 'Sent!'; + setTimeout(() => { btn.textContent = 'Run'; }, 1500); + addMessage('system', 'Command sent to terminal.'); + } catch(e) { setStatus('Run failed: ' + e.message); } +} + +// ── Commander identity greeting ────────────────────────────────────────── + +(function commanderGreeting() { + const ident = window.__alfredIdentity || {}; + const greetEl = document.getElementById('greetingMsg'); + if (!greetEl) return; + const contentEl = greetEl.querySelector('div:not(.sender)'); + if (!contentEl) return; + + const isCommander = ident.ide_client_id === 33; + const name = ident.ide_name || 'there'; + + if (isCommander) { + contentEl.innerHTML = 'Commander on deck. All systems nominal.
' + + 'Full workspace intelligence active. Voice, code actions, file ops, git — everything is live.
' + + 'Sonnet 4.6 default · ' + new Date().toLocaleDateString('en-US', {weekday:'long', month:'long', day:'numeric'}) + ' · GoForge synced'; + } else if (name && name !== 'there') { + contentEl.innerHTML = 'Welcome, ' + name + '. Alfred IDE is ready.
' + + 'Type a question, paste code, attach files, or use voice. I have full context of your workspace.'; + } else { + contentEl.innerHTML = 'Alfred IDE ready. Full AI coding assistant at your service.
' + + 'Speak or type. Sonnet 4.6 connected.'; + } +})(); + setStatus('Ready'); diff --git a/extensions.json b/extensions.json index 32ecc08..d18ff45 100644 --- a/extensions.json +++ b/extensions.json @@ -5,8 +5,6 @@ "unwantedRecommendations": [ "ms-vscode-remote.remote-ssh", "ms-vscode.remote-server", - "ms-vscode.remote-explorer", - "github.copilot", - "github.copilot-chat" + "ms-vscode.remote-explorer" ] } diff --git a/package.json b/package.json index af99615..7ca12a1 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,11 @@ "description": "Fresh Alfred panel: all agents & models, voice STT/TTS, attachments, hands-free. IDE shortcuts are explicit buttons — chat text always goes to AI.", "version": "1.0.1", "publisher": "gositeme", + "repository": { + "type": "git", + "url": "https://alfredlinux.com/forge/commander/alfred-ide.git" + }, + "homepage": "https://alfredlinux.com/forge/commander/alfred-ide", "engines": { "vscode": "^1.70.0" },