v1.1.0: Full workspace intelligence engine, Commander identity system, code block actions (copy/insert/run), rich context injection, GoForge-only, purged GitHub refs

- 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
This commit is contained in:
Alfred 2026-04-07 12:11:42 -04:00
parent fd7361e044
commit d32f46dc59
3 changed files with 618 additions and 22 deletions

View File

@ -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; }
</style>
</head>
<body>
@ -950,11 +1402,15 @@ function getWebviewContent(injectedToken, injectedIdentity) {
<button type="button" class="ide-q-btn" id="acIdePalette" title="Command palette">Commands</button>
<button type="button" class="ide-q-btn" id="acIdeSplit" title="Split editor">Split</button>
<button type="button" class="ide-q-btn" id="acIdeNew" title="New untitled file">New file</button>
<button type="button" class="ide-q-btn" id="acIdeGit" title="Open source control">Git</button>
<button type="button" class="ide-q-btn" id="acIdeProblems" title="Show problems panel">Problems</button>
<button type="button" class="ide-q-btn" id="acIdeSearch" title="Search across files">Search</button>
<button type="button" class="ide-q-btn" id="acIdeFormat" title="Format document">Format</button>
</div>
<div class="chat-area" id="chatArea">
<div class="message alfred">
<div class="message alfred" id="greetingMsg">
<div class="sender">Alfred</div>
<div>Ready. Claude Sonnet 4 connected. Speak or type.</div>
<div>Initializing workspace intelligence...</div>
</div>
</div>
<div class="attach-panel empty" id="attachPanel"></div>
@ -1616,8 +2072,21 @@ function scheduleHandsFreeRecord() {
// ── Message formatting ─────────────────────────────────────────────────────
function formatResponse(text) {
let blockId = 0;
return text
.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g, '<pre><code>$2</code></pre>')
.replace(/\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g, function(match, lang, code) {
blockId++;
const escapedCode = code.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
const safeLang = (lang || 'text').replace(/[^a-zA-Z0-9]/g, '');
return '<div class="code-block-wrap" data-block-id="cb' + blockId + '">' +
'<div class="code-block-header"><span class="code-lang">' + safeLang + '</span>' +
'<button class="cb-btn cb-copy" onclick="copyCodeBlock(this)" title="Copy to clipboard">Copy</button>' +
'<button class="cb-btn cb-insert" onclick="insertCodeBlock(this)" title="Insert at cursor">Insert</button>' +
((['sh','bash','zsh','shell','cmd','powershell','terminal'].includes(safeLang)) ?
'<button class="cb-btn cb-run" onclick="runCodeBlock(this)" title="Run in terminal">Run</button>' : '') +
'</div>' +
'<pre><code data-code="' + btoa(unescape(encodeURIComponent(code))) + '">' + escapedCode + '</code></pre></div>';
})
.replace(/\`([^\`]+)\`/g, '<code>$1</code>')
.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>')
.replace(/\\n/g, '<br>');
@ -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 = '<strong>Commander on deck.</strong> All systems nominal.<br>' +
'<span style="color:#e2b340;">Full workspace intelligence active. Voice, code actions, file ops, git — everything is live.</span><br>' +
'<span style="font-size:10px;color:#8b949e;">Sonnet 4.6 default · ' + new Date().toLocaleDateString('en-US', {weekday:'long', month:'long', day:'numeric'}) + ' · GoForge synced</span>';
} else if (name && name !== 'there') {
contentEl.innerHTML = 'Welcome, <strong>' + name + '</strong>. Alfred IDE is ready.<br>' +
'<span style="font-size:10px;color:#8b949e;">Type a question, paste code, attach files, or use voice. I have full context of your workspace.</span>';
} else {
contentEl.innerHTML = 'Alfred IDE ready. Full AI coding assistant at your service.<br>' +
'<span style="font-size:10px;color:#8b949e;">Speak or type. Sonnet 4.6 connected.</span>';
}
})();
setStatus('Ready');
</script>
</body>

View File

@ -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"
]
}

View File

@ -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"
},