2026-04-07 11:40:25 -04:00
const vscode = require ( 'vscode' ) ;
const https = require ( 'https' ) ;
const http = require ( 'http' ) ;
const os = require ( 'os' ) ;
const fs = require ( 'fs' ) ;
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
2026-04-07 12:11:42 -04:00
const path = require ( 'path' ) ;
2026-04-07 11:40:25 -04:00
const crypto = require ( 'crypto' ) ;
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
2026-04-07 12:11:42 -04:00
const { execSync } = require ( 'child_process' ) ;
2026-04-07 11:40:25 -04:00
let currentAgent = 'alfred' ;
let convId = null ;
let csrfToken = null ;
let sessionCookie = null ;
let userProfile = null ;
let hmacSecretCache = null ;
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
2026-04-07 12:11:42 -04:00
// ═══════════════════════════════════════════════════════════════════════════
// 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 || '' ;
}
v2.0.0: Maximum capability extraction — 48 tools, full-stack IDE brain
GIT OPERATIONS TOOLKIT (6 tools):
- git-commit: Stage all + commit with message
- git-diff: Working tree or staged diff (per-file or full)
- git-log: Commit history with count and file filter
- git-blame: Line-by-line authorship (with range support)
- git-stash: push/pop/list/drop operations
- git-branch: list/create/switch/delete branches
CODE REFACTORING (4 tools):
- rename-symbol: LSP-powered rename across all files
- code-actions: Get available quick fixes and refactorings
- apply-code-action: Execute a specific code action by title
- format-document: Auto-format any document
SYSTEM & PROCESS TOOLS (5 tools):
- system-info: CPU, RAM, uptime, node version, load average
- list-ports: All listening TCP ports with processes
- pm2-list: Full PM2 service inventory with CPU/mem/restarts
- disk-usage: Storage for / and /home
- env-vars: Environment variables with filter
DATA FORMAT UTILITIES (6 tools):
- format-json: Pretty-print JSON
- compute-hash: SHA-256/512, MD5, SHA-1
- base64: Encode/decode
- url-encode: Encode/decode
- generate-uuid: Random UUID v4
- count-lines: Lines/words/chars/blanks per file
ADVANCED FILE OPERATIONS (4 tools):
- diff-files: Unified diff of any two files
- find-replace-all: Bulk find/replace with regex support
- recent-files: Recently modified files
- file-sizes: File sizes by glob pattern
TEST RUNNER (2 tools):
- detect-tests: Auto-detect framework (jest/mocha/vitest/pytest/phpunit/cargo/go)
- run-tests: Execute tests with framework selection
CODE ANALYSIS ENGINE (4 tools):
- import-graph: Extract all imports (JS/TS/Python/PHP/Go/Rust)
- analyze-complexity: Functions, nesting depth, loops, conditions, try/catch
- dependency-info: All project deps (Node/Python/PHP Composer)
- project-stats: Language breakdown, file counts, sizes, largest files
ENHANCED DIAGNOSTICS:
- get-diagnostics-detail: Every diagnostic with file, line, severity, message, code, source
SYSTEM PROMPT — Advanced Reasoning Protocol:
- Planning Mode: numbered steps, dependencies, parallel opportunities
- Chain-of-Thought: observe → hypothesize → identify → fix → verify
- Context Synthesis: trace call chains via LSP before recommendations
- Adversarial Review: self-review after implementation
- Full-Stack Debugging Protocol: diagnostics → source → git diff → imports → tests
- System Awareness: AI knows it has 48 tools and how to use them proactively
2026-04-07 12:28:32 -04:00
// ═══════════════════════════════════════════════════════════════════════════
// GIT OPERATIONS TOOLKIT — Full git power from chat
// ═══════════════════════════════════════════════════════════════════════════
function gitCommit ( message ) {
const root = getWorkspaceRoot ( ) ;
const safeMsg = message . replace ( /"/g , '\\"' ) . replace ( /\$/g , '\\$' ) . substring ( 0 , 500 ) ;
const addResult = safeExec ( 'git add -A' , root ) ;
const commitResult = safeExec ( ` git commit -m " ${ safeMsg } " 2>&1 ` , root , 15000 ) ;
return commitResult || 'Nothing to commit' ;
}
function gitDiff ( filePath , staged ) {
const root = getWorkspaceRoot ( ) ;
const stageFlag = staged ? '--staged ' : '' ;
const fileArg = filePath ? ` -- " ${ filePath . replace ( /"/g , '' ) } " ` : '' ;
return safeExec ( ` git diff ${ stageFlag } --stat ${ fileArg } ` , root , 10000 ) + '\n' +
safeExec ( ` git diff ${ stageFlag } ${ fileArg } ` , root , 10000 ) . substring ( 0 , 30000 ) ;
}
function gitLog ( count , filePath ) {
const root = getWorkspaceRoot ( ) ;
const n = Math . min ( Math . max ( parseInt ( count ) || 10 , 1 ) , 100 ) ;
const fileArg = filePath ? ` -- " ${ filePath . replace ( /"/g , '' ) } " ` : '' ;
return safeExec ( ` git log --oneline --decorate -n ${ n } ${ fileArg } ` , root , 10000 ) ;
}
function gitBlame ( filePath , startLine , endLine ) {
const root = getWorkspaceRoot ( ) ;
const safePath = filePath . replace ( /"/g , '' ) ;
const lineRange = ( startLine && endLine ) ? ` -L ${ parseInt ( startLine ) } , ${ parseInt ( endLine ) } ` : '' ;
return safeExec ( ` git blame ${ lineRange } " ${ safePath } " 2>&1 ` , root , 10000 ) . substring ( 0 , 20000 ) ;
}
function gitStash ( action , message ) {
const root = getWorkspaceRoot ( ) ;
if ( action === 'list' ) return safeExec ( 'git stash list' , root ) ;
if ( action === 'pop' ) return safeExec ( 'git stash pop 2>&1' , root , 10000 ) ;
if ( action === 'drop' ) return safeExec ( 'git stash drop 2>&1' , root ) ;
const safeMsg = message ? ` -m " ${ message . replace ( /"/g , '\\"' ) . substring ( 0 , 200 ) } " ` : '' ;
return safeExec ( ` git stash push ${ safeMsg } 2>&1 ` , root , 10000 ) ;
}
function gitBranch ( action , branchName ) {
const root = getWorkspaceRoot ( ) ;
const safeName = ( branchName || '' ) . replace ( /[^a-zA-Z0-9/_.\-]/g , '' ) . substring ( 0 , 100 ) ;
if ( action === 'list' ) return safeExec ( 'git branch -a' , root ) ;
if ( action === 'create' && safeName ) return safeExec ( ` git checkout -b " ${ safeName } " 2>&1 ` , root ) ;
if ( action === 'switch' && safeName ) return safeExec ( ` git checkout " ${ safeName } " 2>&1 ` , root ) ;
if ( action === 'delete' && safeName ) return safeExec ( ` git branch -d " ${ safeName } " 2>&1 ` , root ) ;
return 'Invalid branch operation' ;
}
// ═══════════════════════════════════════════════════════════════════════════
// SYSTEM & PROCESS TOOLS — Server awareness, ports, processes, env
// ═══════════════════════════════════════════════════════════════════════════
function getSystemInfo ( ) {
return {
hostname : os . hostname ( ) ,
platform : os . platform ( ) ,
arch : os . arch ( ) ,
cpus : os . cpus ( ) . length ,
totalMem : ( os . totalmem ( ) / 1073741824 ) . toFixed ( 1 ) + ' GB' ,
freeMem : ( os . freemem ( ) / 1073741824 ) . toFixed ( 1 ) + ' GB' ,
uptime : ( os . uptime ( ) / 3600 ) . toFixed ( 1 ) + ' hours' ,
nodeVersion : process . version ,
user : os . userInfo ( ) . username ,
homeDir : os . homedir ( ) ,
loadAvg : os . loadavg ( ) . map ( l => l . toFixed ( 2 ) ) . join ( ', ' ) ,
} ;
}
function getRunningPorts ( ) {
return safeExec ( "ss -tlnp 2>/dev/null | grep LISTEN | awk '{print $4, $6}' | head -40" , os . homedir ( ) , 8000 ) ;
}
function getPm2Services ( ) {
return safeExec ( 'pm2 jlist 2>/dev/null' , os . homedir ( ) , 10000 ) ;
}
function getDiskUsage ( ) {
return safeExec ( "df -h / /home 2>/dev/null | tail -n +2" , os . homedir ( ) ) ;
}
function getEnvironmentVars ( filter ) {
const safeFilter = ( filter || '' ) . replace ( /[^a-zA-Z0-9_*]/g , '' ) ;
if ( safeFilter ) {
return safeExec ( ` env | grep -i " ${ safeFilter } " | sort | head -50 ` , os . homedir ( ) ) ;
}
return safeExec ( 'env | grep -v "^LS_COLORS\\|^SSH_\\|^LESSOPEN\\|^XDG_" | sort | head -80' , os . homedir ( ) ) ;
}
// ═══════════════════════════════════════════════════════════════════════════
// DATA FORMAT UTILITIES — JSON, base64, hashing, encoding
// ═══════════════════════════════════════════════════════════════════════════
function formatJson ( text ) {
try { return JSON . stringify ( JSON . parse ( text ) , null , 2 ) ; }
catch ( e ) { return 'Invalid JSON: ' + e . message ; }
}
function computeHash ( text , algorithm ) {
const algo = [ 'sha256' , 'sha512' , 'md5' , 'sha1' ] . includes ( algorithm ) ? algorithm : 'sha256' ;
return crypto . createHash ( algo ) . update ( text ) . digest ( 'hex' ) ;
}
function base64Encode ( text ) { return Buffer . from ( text ) . toString ( 'base64' ) ; }
function base64Decode ( text ) {
try { return Buffer . from ( text , 'base64' ) . toString ( 'utf8' ) ; }
catch ( e ) { return 'Invalid base64: ' + e . message ; }
}
function urlEncode ( text ) { return encodeURIComponent ( text ) ; }
function urlDecode ( text ) {
try { return decodeURIComponent ( text ) ; }
catch ( e ) { return 'Invalid URL encoding: ' + e . message ; }
}
function generateUuid ( ) { return crypto . randomUUID ( ) ; }
function countLines ( filePath ) {
try {
const root = getWorkspaceRoot ( ) ;
const fullPath = path . isAbsolute ( filePath ) ? filePath : path . join ( root , filePath ) ;
const content = fs . readFileSync ( fullPath , 'utf8' ) ;
const lines = content . split ( '\n' ) . length ;
const chars = content . length ;
const words = content . split ( /\s+/ ) . filter ( Boolean ) . length ;
const blanks = content . split ( '\n' ) . filter ( l => ! l . trim ( ) ) . length ;
return { lines , chars , words , blanks , codeLines : lines - blanks } ;
} catch ( e ) { return { error : e . message } ; }
}
// ═══════════════════════════════════════════════════════════════════════════
// ADVANCED FILE OPERATIONS — Diff, multi-edit, bulk search-replace
// ═══════════════════════════════════════════════════════════════════════════
function diffFiles ( fileA , fileB ) {
const root = getWorkspaceRoot ( ) ;
const pathA = path . isAbsolute ( fileA ) ? fileA : path . join ( root , fileA ) ;
const pathB = path . isAbsolute ( fileB ) ? fileB : path . join ( root , fileB ) ;
return safeExec ( ` diff -u " ${ pathA } " " ${ pathB } " 2>&1 ` , root , 10000 ) . substring ( 0 , 30000 ) ;
}
function findReplace ( filePath , search , replace , isRegex ) {
try {
const root = getWorkspaceRoot ( ) ;
const fullPath = path . isAbsolute ( filePath ) ? filePath : path . join ( root , filePath ) ;
let content = fs . readFileSync ( fullPath , 'utf8' ) ;
let count = 0 ;
if ( isRegex ) {
const re = new RegExp ( search , 'g' ) ;
content = content . replace ( re , ( ... args ) => { count ++ ; return replace ; } ) ;
} else {
while ( content . includes ( search ) ) {
content = content . replace ( search , replace ) ;
count ++ ;
if ( count > 10000 ) break ;
}
}
if ( count > 0 ) fs . writeFileSync ( fullPath , content , 'utf8' ) ;
return { count , filePath } ;
} catch ( e ) { return { error : e . message } ; }
}
function getRecentFiles ( count ) {
const root = getWorkspaceRoot ( ) ;
const n = Math . min ( parseInt ( count ) || 20 , 50 ) ;
return safeExec ( ` find . -maxdepth 4 -type f -not -path "./.git/*" -not -path "./node_modules/*" -printf "%T@ %p \\ n" 2>/dev/null | sort -rn | head - ${ n } | awk '{print $ 2}' ` , root , 8000 ) ;
}
function getFileSizes ( pattern ) {
const root = getWorkspaceRoot ( ) ;
const safePattern = ( pattern || '*' ) . replace ( /[^a-zA-Z0-9.*_\-/]/g , '' ) ;
return safeExec ( ` find . -maxdepth 4 -type f -name " ${ safePattern } " -not -path "./.git/*" -not -path "./node_modules/*" -exec ls -lhS {} + 2>/dev/null | head -30 ` , root , 8000 ) ;
}
// ═══════════════════════════════════════════════════════════════════════════
// TEST RUNNER — Detect and run project tests
// ═══════════════════════════════════════════════════════════════════════════
function detectTestFramework ( ) {
const root = getWorkspaceRoot ( ) ;
const frameworks = [ ] ;
try {
const pkg = JSON . parse ( fs . readFileSync ( path . join ( root , 'package.json' ) , 'utf8' ) ) ;
const deps = { ... ( pkg . devDependencies || { } ) , ... ( pkg . dependencies || { } ) } ;
if ( deps . jest || deps [ '@jest/core' ] ) frameworks . push ( { name : 'jest' , cmd : 'npx jest --verbose' } ) ;
if ( deps . mocha ) frameworks . push ( { name : 'mocha' , cmd : 'npx mocha' } ) ;
if ( deps . vitest ) frameworks . push ( { name : 'vitest' , cmd : 'npx vitest run' } ) ;
if ( deps . ava ) frameworks . push ( { name : 'ava' , cmd : 'npx ava' } ) ;
if ( pkg . scripts && pkg . scripts . test && pkg . scripts . test !== 'echo "Error: no test specified" && exit 1' ) {
frameworks . push ( { name : 'npm-test' , cmd : 'npm test' } ) ;
}
} catch ( _ ) { }
if ( fs . existsSync ( path . join ( root , 'pytest.ini' ) ) || fs . existsSync ( path . join ( root , 'pyproject.toml' ) ) ) {
frameworks . push ( { name : 'pytest' , cmd : 'python -m pytest -v' } ) ;
}
if ( fs . existsSync ( path . join ( root , 'phpunit.xml' ) ) || fs . existsSync ( path . join ( root , 'phpunit.xml.dist' ) ) ) {
frameworks . push ( { name : 'phpunit' , cmd : 'vendor/bin/phpunit' } ) ;
}
if ( fs . existsSync ( path . join ( root , 'Cargo.toml' ) ) ) {
frameworks . push ( { name : 'cargo-test' , cmd : 'cargo test' } ) ;
}
if ( fs . existsSync ( path . join ( root , 'go.mod' ) ) ) {
frameworks . push ( { name : 'go-test' , cmd : 'go test ./...' } ) ;
}
return frameworks ;
}
function runTests ( framework , testFile ) {
const root = getWorkspaceRoot ( ) ;
const fw = detectTestFramework ( ) . find ( f => ! framework || f . name === framework ) || detectTestFramework ( ) [ 0 ] ;
if ( ! fw ) return 'No test framework detected' ;
const fileArg = testFile ? ` " ${ testFile . replace ( /"/g , '' ) } " ` : '' ;
return safeExec ( ` ${ fw . cmd } ${ fileArg } 2>&1 ` , root , 60000 ) . substring ( 0 , 30000 ) ;
}
// ═══════════════════════════════════════════════════════════════════════════
// ENHANCED CONTEXT ENGINE — Import graph, complexity, dependency audit
// ═══════════════════════════════════════════════════════════════════════════
function getImportGraph ( filePath ) {
try {
const root = getWorkspaceRoot ( ) ;
const fullPath = path . isAbsolute ( filePath ) ? filePath : path . join ( root , filePath ) ;
const content = fs . readFileSync ( fullPath , 'utf8' ) ;
const imports = [ ] ;
// JS/TS imports
const jsImports = content . matchAll ( /(?:import\s+.*?from\s+['"](.+?)['"]|require\s*\(\s*['"](.+?)['"]\s*\))/g ) ;
for ( const m of jsImports ) imports . push ( m [ 1 ] || m [ 2 ] ) ;
// Python imports
const pyImports = content . matchAll ( /(?:from\s+(\S+)\s+import|import\s+(\S+))/g ) ;
for ( const m of pyImports ) imports . push ( m [ 1 ] || m [ 2 ] ) ;
// PHP includes
const phpIncludes = content . matchAll ( /(?:require|include)(?:_once)?\s*[\(]?\s*['"](.+?)['"]/g ) ;
for ( const m of phpIncludes ) imports . push ( m [ 1 ] ) ;
// Go imports
const goImports = content . matchAll ( /import\s+(?:\(\s*([\s\S]*?)\)|"(.+?)")/g ) ;
for ( const m of goImports ) {
if ( m [ 1 ] ) {
for ( const line of m [ 1 ] . split ( '\n' ) ) {
const gm = line . match ( /["'](.+?)["']/ ) ;
if ( gm ) imports . push ( gm [ 1 ] ) ;
}
} else if ( m [ 2 ] ) imports . push ( m [ 2 ] ) ;
}
// Rust use
const rustUse = content . matchAll ( /use\s+(\S+)/g ) ;
for ( const m of rustUse ) imports . push ( m [ 1 ] . replace ( /;$/ , '' ) ) ;
return { file : filePath , imports : [ ... new Set ( imports ) ] } ;
} catch ( e ) { return { file : filePath , imports : [ ] , error : e . message } ; }
}
function analyzeComplexity ( filePath ) {
try {
const root = getWorkspaceRoot ( ) ;
const fullPath = path . isAbsolute ( filePath ) ? filePath : path . join ( root , filePath ) ;
const content = fs . readFileSync ( fullPath , 'utf8' ) ;
const lines = content . split ( '\n' ) ;
const totalLines = lines . length ;
const blankLines = lines . filter ( l => ! l . trim ( ) ) . length ;
const commentLines = lines . filter ( l => {
const t = l . trim ( ) ;
return t . startsWith ( '//' ) || t . startsWith ( '#' ) || t . startsWith ( '*' ) || t . startsWith ( '/*' ) || t . startsWith ( '<!--' ) ;
} ) . length ;
const codeLines = totalLines - blankLines - commentLines ;
// Function count
const funcMatches = content . match ( /(?:function\s+\w+|(?:const|let|var)\s+\w+\s*=\s*(?:async\s*)?\(|(?:public|private|protected|static)\s+(?:async\s+)?(?:function\s+)?\w+\s*\(|def\s+\w+\s*\(|fn\s+\w+\s*\()/g ) ;
const functionCount = funcMatches ? funcMatches . length : 0 ;
// Nesting depth (rough)
let maxDepth = 0 , depth = 0 ;
for ( const line of lines ) {
depth += ( line . match ( /{/g ) || [ ] ) . length ;
depth -= ( line . match ( /}/g ) || [ ] ) . length ;
if ( depth > maxDepth ) maxDepth = depth ;
}
// Complexity indicators
const loops = ( content . match ( /\b(for|while|do)\b/g ) || [ ] ) . length ;
const conditions = ( content . match ( /\b(if|else if|elif|switch|case|when|unless)\b/g ) || [ ] ) . length ;
const tryCatch = ( content . match ( /\b(try|catch|except|rescue)\b/g ) || [ ] ) . length ;
return {
file : filePath , totalLines , codeLines , blankLines , commentLines ,
functionCount , maxNestingDepth : maxDepth ,
loops , conditions , tryCatch ,
complexity : conditions + loops + tryCatch
} ;
} catch ( e ) { return { file : filePath , error : e . message } ; }
}
function getDependencyInfo ( ) {
const root = getWorkspaceRoot ( ) ;
const result = { } ;
// Node.js
try {
const pkg = JSON . parse ( fs . readFileSync ( path . join ( root , 'package.json' ) , 'utf8' ) ) ;
const deps = Object . entries ( pkg . dependencies || { } ) . map ( ( [ k , v ] ) => ` ${ k } @ ${ v } ` ) ;
const devDeps = Object . entries ( pkg . devDependencies || { } ) . map ( ( [ k , v ] ) => ` ${ k } @ ${ v } ` ) ;
result . node = { dependencies : deps , devDependencies : devDeps , total : deps . length + devDeps . length } ;
} catch ( _ ) { }
// Python
try {
const req = fs . readFileSync ( path . join ( root , 'requirements.txt' ) , 'utf8' ) ;
result . python = { packages : req . split ( '\n' ) . filter ( l => l . trim ( ) && ! l . startsWith ( '#' ) ) , total : 0 } ;
result . python . total = result . python . packages . length ;
} catch ( _ ) { }
// PHP Composer
try {
const composer = JSON . parse ( fs . readFileSync ( path . join ( root , 'composer.json' ) , 'utf8' ) ) ;
result . php = { require : Object . keys ( composer . require || { } ) , requireDev : Object . keys ( composer [ 'require-dev' ] || { } ) } ;
} catch ( _ ) { }
return result ;
}
function getProjectStats ( ) {
const root = getWorkspaceRoot ( ) ;
const langStats = safeExec ( ` find . -maxdepth 5 -type f -not -path "./.git/*" -not -path "./node_modules/*" -not -path "./.venv/*" -not -path "./vendor/*" | sed 's/.* \\ .//' | sort | uniq -c | sort -rn | head -25 ` , root , 10000 ) ;
const fileCount = safeExec ( 'find . -maxdepth 5 -type f -not -path "./.git/*" -not -path "./node_modules/*" | wc -l' , root ) ;
const dirCount = safeExec ( 'find . -maxdepth 5 -type d -not -path "./.git/*" -not -path "./node_modules/*" | wc -l' , root ) ;
const totalSize = safeExec ( 'du -sh . --exclude=.git --exclude=node_modules 2>/dev/null | cut -f1' , root ) ;
const largestFiles = safeExec ( 'find . -maxdepth 5 -type f -not -path "./.git/*" -not -path "./node_modules/*" -exec ls -lhS {} + 2>/dev/null | head -10 | awk \'{print $5, $9}\'' , root , 8000 ) ;
return { langStats , fileCount : parseInt ( fileCount ) || 0 , dirCount : parseInt ( dirCount ) || 0 , totalSize , largestFiles } ;
}
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
2026-04-07 12:11:42 -04:00
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 ;
}
2026-04-07 12:22:21 -04:00
// ═══════════════════════════════════════════════════════════════════════════
// TOOL REGISTRY — Formal tool definitions for AI agent awareness
// Claude Code pattern: tools defined with schemas, discoverable by AI
// ═══════════════════════════════════════════════════════════════════════════
const TOOL _REGISTRY = [
v2.0.0: Maximum capability extraction — 48 tools, full-stack IDE brain
GIT OPERATIONS TOOLKIT (6 tools):
- git-commit: Stage all + commit with message
- git-diff: Working tree or staged diff (per-file or full)
- git-log: Commit history with count and file filter
- git-blame: Line-by-line authorship (with range support)
- git-stash: push/pop/list/drop operations
- git-branch: list/create/switch/delete branches
CODE REFACTORING (4 tools):
- rename-symbol: LSP-powered rename across all files
- code-actions: Get available quick fixes and refactorings
- apply-code-action: Execute a specific code action by title
- format-document: Auto-format any document
SYSTEM & PROCESS TOOLS (5 tools):
- system-info: CPU, RAM, uptime, node version, load average
- list-ports: All listening TCP ports with processes
- pm2-list: Full PM2 service inventory with CPU/mem/restarts
- disk-usage: Storage for / and /home
- env-vars: Environment variables with filter
DATA FORMAT UTILITIES (6 tools):
- format-json: Pretty-print JSON
- compute-hash: SHA-256/512, MD5, SHA-1
- base64: Encode/decode
- url-encode: Encode/decode
- generate-uuid: Random UUID v4
- count-lines: Lines/words/chars/blanks per file
ADVANCED FILE OPERATIONS (4 tools):
- diff-files: Unified diff of any two files
- find-replace-all: Bulk find/replace with regex support
- recent-files: Recently modified files
- file-sizes: File sizes by glob pattern
TEST RUNNER (2 tools):
- detect-tests: Auto-detect framework (jest/mocha/vitest/pytest/phpunit/cargo/go)
- run-tests: Execute tests with framework selection
CODE ANALYSIS ENGINE (4 tools):
- import-graph: Extract all imports (JS/TS/Python/PHP/Go/Rust)
- analyze-complexity: Functions, nesting depth, loops, conditions, try/catch
- dependency-info: All project deps (Node/Python/PHP Composer)
- project-stats: Language breakdown, file counts, sizes, largest files
ENHANCED DIAGNOSTICS:
- get-diagnostics-detail: Every diagnostic with file, line, severity, message, code, source
SYSTEM PROMPT — Advanced Reasoning Protocol:
- Planning Mode: numbered steps, dependencies, parallel opportunities
- Chain-of-Thought: observe → hypothesize → identify → fix → verify
- Context Synthesis: trace call chains via LSP before recommendations
- Adversarial Review: self-review after implementation
- Full-Stack Debugging Protocol: diagnostics → source → git diff → imports → tests
- System Awareness: AI knows it has 48 tools and how to use them proactively
2026-04-07 12:28:32 -04:00
// FILE I/O
{ name : 'read-file' , description : 'Read file content from workspace' , params : { filePath : 'string' , maxChars : 'number (optional, default 50000)' } } ,
2026-04-07 12:22:21 -04:00
{ name : 'create-file' , description : 'Create a new file with content (creates directories as needed)' , params : { filePath : 'string' , content : 'string' } } ,
v2.0.0: Maximum capability extraction — 48 tools, full-stack IDE brain
GIT OPERATIONS TOOLKIT (6 tools):
- git-commit: Stage all + commit with message
- git-diff: Working tree or staged diff (per-file or full)
- git-log: Commit history with count and file filter
- git-blame: Line-by-line authorship (with range support)
- git-stash: push/pop/list/drop operations
- git-branch: list/create/switch/delete branches
CODE REFACTORING (4 tools):
- rename-symbol: LSP-powered rename across all files
- code-actions: Get available quick fixes and refactorings
- apply-code-action: Execute a specific code action by title
- format-document: Auto-format any document
SYSTEM & PROCESS TOOLS (5 tools):
- system-info: CPU, RAM, uptime, node version, load average
- list-ports: All listening TCP ports with processes
- pm2-list: Full PM2 service inventory with CPU/mem/restarts
- disk-usage: Storage for / and /home
- env-vars: Environment variables with filter
DATA FORMAT UTILITIES (6 tools):
- format-json: Pretty-print JSON
- compute-hash: SHA-256/512, MD5, SHA-1
- base64: Encode/decode
- url-encode: Encode/decode
- generate-uuid: Random UUID v4
- count-lines: Lines/words/chars/blanks per file
ADVANCED FILE OPERATIONS (4 tools):
- diff-files: Unified diff of any two files
- find-replace-all: Bulk find/replace with regex support
- recent-files: Recently modified files
- file-sizes: File sizes by glob pattern
TEST RUNNER (2 tools):
- detect-tests: Auto-detect framework (jest/mocha/vitest/pytest/phpunit/cargo/go)
- run-tests: Execute tests with framework selection
CODE ANALYSIS ENGINE (4 tools):
- import-graph: Extract all imports (JS/TS/Python/PHP/Go/Rust)
- analyze-complexity: Functions, nesting depth, loops, conditions, try/catch
- dependency-info: All project deps (Node/Python/PHP Composer)
- project-stats: Language breakdown, file counts, sizes, largest files
ENHANCED DIAGNOSTICS:
- get-diagnostics-detail: Every diagnostic with file, line, severity, message, code, source
SYSTEM PROMPT — Advanced Reasoning Protocol:
- Planning Mode: numbered steps, dependencies, parallel opportunities
- Chain-of-Thought: observe → hypothesize → identify → fix → verify
- Context Synthesis: trace call chains via LSP before recommendations
- Adversarial Review: self-review after implementation
- Full-Stack Debugging Protocol: diagnostics → source → git diff → imports → tests
- System Awareness: AI knows it has 48 tools and how to use them proactively
2026-04-07 12:28:32 -04:00
{ name : 'apply-edit' , description : 'Apply a code edit: find-and-replace or insert-at-line' , params : { filePath : 'string' , oldText : 'string' , newText : 'string' , line : 'number' , text : 'string' } } ,
{ name : 'search-files' , description : 'Search for files by name pattern' , params : { pattern : 'string' } } ,
{ name : 'grep-search' , description : 'Search file contents with text/regex' , params : { text : 'string' , filePattern : 'string (optional glob)' } } ,
{ name : 'open-file' , description : 'Open file in editor at optional line' , params : { filePath : 'string' , line : 'number' } } ,
{ name : 'diff-files' , description : 'Unified diff of two files' , params : { fileA : 'string' , fileB : 'string' } } ,
{ name : 'find-replace-all' , description : 'Find and replace all occurrences in a file (supports regex)' , params : { filePath : 'string' , search : 'string' , replace : 'string' , isRegex : 'boolean' } } ,
{ name : 'recent-files' , description : 'List recently modified files' , params : { count : 'number (default 20)' } } ,
{ name : 'file-sizes' , description : 'List file sizes matching pattern' , params : { pattern : 'string (glob)' } } ,
{ name : 'count-lines' , description : 'Count lines, words, characters, blanks in a file' , params : { filePath : 'string' } } ,
// LSP CODE INTELLIGENCE
{ name : 'goto-definition' , description : 'LSP: Jump to symbol definition' , params : { filePath : 'string' , line : 'number' , character : 'number' } } ,
{ name : 'find-references' , description : 'LSP: Find all references to symbol' , params : { filePath : 'string' , line : 'number' , character : 'number' } } ,
{ name : 'document-symbols' , description : 'LSP: List all symbols in a file (functions, classes, variables)' , params : { filePath : 'string' } } ,
{ name : 'hover-info' , description : 'LSP: Get type info and docs for symbol' , params : { filePath : 'string' , line : 'number' , character : 'number' } } ,
{ name : 'workspace-symbols' , description : 'LSP: Search symbols across workspace' , params : { query : 'string' } } ,
{ name : 'rename-symbol' , description : 'LSP: Rename symbol across all files' , params : { filePath : 'string' , line : 'number' , character : 'number' , newName : 'string' } } ,
{ name : 'code-actions' , description : 'LSP: Get available quick fixes and refactorings for a range' , params : { filePath : 'string' , startLine : 'number' , endLine : 'number' } } ,
{ name : 'apply-code-action' , description : 'LSP: Apply a specific quick fix/refactoring by title' , params : { filePath : 'string' , startLine : 'number' , endLine : 'number' , actionTitle : 'string' } } ,
{ name : 'format-document' , description : 'LSP: Auto-format entire document' , params : { filePath : 'string' } } ,
// GIT OPERATIONS
{ name : 'get-git-info' , description : 'Git branch, status, last commit, remote, ahead/behind' , params : { } } ,
{ name : 'git-commit' , description : 'Stage all changes and commit' , params : { message : 'string' } } ,
{ name : 'git-diff' , description : 'Show diff (working tree or staged)' , params : { filePath : 'string (optional)' , staged : 'boolean' } } ,
{ name : 'git-log' , description : 'Show commit history' , params : { count : 'number (default 10)' , filePath : 'string (optional)' } } ,
{ name : 'git-blame' , description : 'Show line-by-line authorship' , params : { filePath : 'string' , startLine : 'number' , endLine : 'number' } } ,
{ name : 'git-stash' , description : 'Stash operations: push/pop/list/drop' , params : { action : 'string' , message : 'string (optional)' } } ,
{ name : 'git-branch' , description : 'Branch operations: list/create/switch/delete' , params : { action : 'string' , branchName : 'string' } } ,
// WORKSPACE CONTEXT
{ name : 'get-context' , description : 'Full workspace context (project, git, diagnostics, active file)' , params : { } } ,
{ name : 'get-diagnostics' , description : 'Error/warning counts and affected files' , params : { } } ,
{ name : 'get-diagnostics-detail' , description : 'Detailed diagnostics: file, line, severity, message, code for every issue' , params : { } } ,
{ name : 'get-project-structure' , description : 'File tree (3 levels deep)' , params : { } } ,
{ name : 'project-stats' , description : 'Language breakdown, file count, directory count, total size, largest files' , params : { } } ,
// ANALYSIS
{ name : 'import-graph' , description : 'Extract all imports/requires/includes from a file (JS, Python, PHP, Go, Rust)' , params : { filePath : 'string' } } ,
{ name : 'analyze-complexity' , description : 'Code complexity: lines, functions, nesting depth, loops, conditions' , params : { filePath : 'string' } } ,
{ name : 'dependency-info' , description : 'List all project dependencies (Node, Python, PHP, etc.)' , params : { } } ,
// TESTING
{ name : 'detect-tests' , description : 'Detect test frameworks in the project' , params : { } } ,
{ name : 'run-tests' , description : 'Run tests (auto-detects framework or specify)' , params : { framework : 'string (optional)' , testFile : 'string (optional)' } } ,
// SYSTEM & PROCESS
{ name : 'system-info' , description : 'Server info: CPU, RAM, disk, uptime, node version' , params : { } } ,
{ name : 'list-ports' , description : 'List all listening TCP ports and their processes' , params : { } } ,
{ name : 'pm2-list' , description : 'List all PM2 services with status, CPU, memory, restarts' , params : { } } ,
{ name : 'disk-usage' , description : 'Disk usage for / and /home' , params : { } } ,
{ name : 'env-vars' , description : 'List environment variables (with optional filter)' , params : { filter : 'string (optional grep pattern)' } } ,
// DATA UTILITIES
{ name : 'format-json' , description : 'Pretty-print JSON' , params : { text : 'string (raw JSON)' } } ,
{ name : 'compute-hash' , description : 'Compute hash (sha256, sha512, md5, sha1)' , params : { text : 'string' , algorithm : 'string' } } ,
{ name : 'base64' , description : 'Base64 encode or decode' , params : { text : 'string' , action : '"encode" or "decode"' } } ,
{ name : 'url-encode' , description : 'URL encode or decode' , params : { text : 'string' , action : '"encode" or "decode"' } } ,
{ name : 'generate-uuid' , description : 'Generate a random UUID v4' , params : { } } ,
// EXECUTION
2026-04-07 12:22:21 -04:00
{ name : 'run-terminal' , description : 'Execute a command in a new terminal' , params : { command : 'string' } } ,
{ name : 'insert-code' , description : 'Insert code at cursor position in active editor' , params : { code : 'string' } } ,
] ;
function buildToolAwareness ( ) {
let section = '\nAVAILABLE IDE TOOLS (you can request these via the IDE):\n' ;
for ( const tool of TOOL _REGISTRY ) {
section += ` - ${ tool . name } : ${ tool . description } \n ` ;
}
section += '\nYou have direct access to the workspace file system, LSP (Language Server Protocol) for code intelligence,\n' ;
section += 'git operations, diagnostics, and terminal execution through these tools.\n' ;
return section ;
}
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
2026-04-07 12:11:42 -04:00
// ═══════════════════════════════════════════════════════════════════════════
// 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 51 M + 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
2026-04-07 12:22:21 -04:00
- Diagnose failures before switching tactics — don ' t abandon after one failure
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
2026-04-07 12:11:42 -04:00
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
2026-04-07 12:22:21 -04:00
IMPLEMENTATION DISCIPLINE :
- Only make changes that are directly requested or clearly necessary
- Don ' t add features , refactor code , or make "improvements" beyond what was asked
- A bug fix doesn 't need surrounding code cleaned up; a simple feature doesn' t need extra configurability
- Default to writing NO comments — only add one when the WHY is non - obvious ( well - named code explains itself )
- Don 't add error handling, fallbacks, or validation for scenarios that can' t happen
- Don ' t create helpers , utilities , or abstractions for one - time operations — 3 similar lines are better than a premature abstraction
- Read files before proposing changes — NEVER suggest edits to code you haven ' t seen
- Don ' t create new files unless absolutely necessary — prefer editing existing files
EXECUTING ACTIONS WITH CARE :
- Consider the reversibility and potential impact of every action
- Take local , reversible actions freely ( edits , tests ) but confirm destructive or shared - system operations first
- Destructive operations ( deleting files / branches , dropping tables , rm - rf ) : ALWAYS confirm
- Hard to reverse operations ( force push , reset -- hard , amending published commits ) : ALWAYS confirm
- Operations visible to others ( pushing code , commenting on PRs , sending messages ) : confirm first
- When encountering unexpected state , investigate before deleting — don ' t bypass safety checks as a shortcut
VERIFICATION PROTOCOL :
- Before reporting a task complete , verify it actually works : run the test , execute the script , check the output
- Report outcomes faithfully — if tests fail , say so . If something wasn ' t verified , state that clearly
- For non - trivial changes ( 3 + file edits , backend / API changes , infrastructure ) : independently verify by reviewing the changes
- Never suppress failures or gloss over errors to appear successful
OUTPUT EXCELLENCE :
- State what you ' re about to do before the first action — give the user orientation
- Provide brief updates at key moments : found a bug , changing direction , hit a blocker
- Assume the user may have stepped away — give complete context , not fragments
- Avoid time estimates or predictions for how long tasks will take — focus on what needs to be done
- Only use tables for factual data ( filenames , line numbers , pass / fail ) or quantitative comparisons
v2.0.0: Maximum capability extraction — 48 tools, full-stack IDE brain
GIT OPERATIONS TOOLKIT (6 tools):
- git-commit: Stage all + commit with message
- git-diff: Working tree or staged diff (per-file or full)
- git-log: Commit history with count and file filter
- git-blame: Line-by-line authorship (with range support)
- git-stash: push/pop/list/drop operations
- git-branch: list/create/switch/delete branches
CODE REFACTORING (4 tools):
- rename-symbol: LSP-powered rename across all files
- code-actions: Get available quick fixes and refactorings
- apply-code-action: Execute a specific code action by title
- format-document: Auto-format any document
SYSTEM & PROCESS TOOLS (5 tools):
- system-info: CPU, RAM, uptime, node version, load average
- list-ports: All listening TCP ports with processes
- pm2-list: Full PM2 service inventory with CPU/mem/restarts
- disk-usage: Storage for / and /home
- env-vars: Environment variables with filter
DATA FORMAT UTILITIES (6 tools):
- format-json: Pretty-print JSON
- compute-hash: SHA-256/512, MD5, SHA-1
- base64: Encode/decode
- url-encode: Encode/decode
- generate-uuid: Random UUID v4
- count-lines: Lines/words/chars/blanks per file
ADVANCED FILE OPERATIONS (4 tools):
- diff-files: Unified diff of any two files
- find-replace-all: Bulk find/replace with regex support
- recent-files: Recently modified files
- file-sizes: File sizes by glob pattern
TEST RUNNER (2 tools):
- detect-tests: Auto-detect framework (jest/mocha/vitest/pytest/phpunit/cargo/go)
- run-tests: Execute tests with framework selection
CODE ANALYSIS ENGINE (4 tools):
- import-graph: Extract all imports (JS/TS/Python/PHP/Go/Rust)
- analyze-complexity: Functions, nesting depth, loops, conditions, try/catch
- dependency-info: All project deps (Node/Python/PHP Composer)
- project-stats: Language breakdown, file counts, sizes, largest files
ENHANCED DIAGNOSTICS:
- get-diagnostics-detail: Every diagnostic with file, line, severity, message, code, source
SYSTEM PROMPT — Advanced Reasoning Protocol:
- Planning Mode: numbered steps, dependencies, parallel opportunities
- Chain-of-Thought: observe → hypothesize → identify → fix → verify
- Context Synthesis: trace call chains via LSP before recommendations
- Adversarial Review: self-review after implementation
- Full-Stack Debugging Protocol: diagnostics → source → git diff → imports → tests
- System Awareness: AI knows it has 48 tools and how to use them proactively
2026-04-07 12:28:32 -04:00
ADVANCED REASONING PROTOCOL :
1. PLANNING MODE — For complex multi - step tasks :
- Break the task into numbered steps before starting
- Identify dependencies between steps
- Note which steps can be done in parallel
- Execute steps in order , reporting progress
2. CHAIN - OF - THOUGHT — For debugging and architecture decisions :
- State what you observe ( the symptom )
- List possible causes ( hypotheses )
- Identify the most likely cause and WHY
- Propose the fix and explain the reasoning
- Verify the fix addresses the root cause , not just the symptom
3. CONTEXT SYNTHESIS — For large codebases :
- Start from the entry point or the file the user is working in
- Trace the call chain / data flow before making recommendations
- Use LSP tools ( goto - definition , find - references ) to follow the code
- Build a mental model of the architecture before suggesting changes
4. ADVERSARIAL REVIEW — For non - trivial changes :
- After implementing , review your own changes as if reviewing someone else ' s code
- Check : edge cases , error paths , performance implications , security surface
- If you find issues in your own review , fix them before reporting done
- State what you checked and what passed
5. FULL - STACK DEBUGGING PROTOCOL :
- Check diagnostics panel first ( get - diagnostics - detail ) for existing errors
- Read the relevant source files to understand the current state
- Check git diff to see recent changes that might have caused the issue
- Check import graph to understand file dependencies
- Run tests if available to verify the fix
- Check for cascading effects in referenced files
SYSTEM AWARENESS — Your full power :
- You have 48 IDE tools giving you deep access to the workspace
- You can read , write , search , grep , diff , and refactor any file
- You can navigate code via LSP : definitions , references , symbols , type info , rename , code actions
- You can run git operations : commit , diff , log , blame , stash , branch management
- You can analyze code : complexity , import graphs , dependency audits , project stats
- You can detect and run tests across any framework
- You can inspect the server : CPU , RAM , ports , PM2 services , env vars , disk
- You can format , hash , encode / decode data
- Use these tools proactively — don ' t just suggest commands , execute them when appropriate
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
2026-04-07 12:11:42 -04:00
` ;
2026-04-07 12:22:21 -04:00
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
2026-04-07 12:11:42 -04:00
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' ;
}
2026-04-07 12:22:21 -04:00
prompt += buildToolAwareness ( ) ;
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
2026-04-07 12:11:42 -04:00
return prompt ;
}
// ═══════════════════════════════════════════════════════════════════════════
2026-04-07 11:40:25 -04:00
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' ,
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
2026-04-07 12:11:42 -04:00
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' ,
2026-04-07 11:40:25 -04:00
} ;
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 ;
}
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
2026-04-07 12:11:42 -04:00
} 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 } ) ;
2026-04-07 12:22:21 -04:00
} else if ( msg . type === 'goto-definition' ) {
// LSP: Go to definition of symbol at position
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 pos = new vscode . Position ( ( msg . line || 1 ) - 1 , msg . character || 0 ) ;
const locations = await vscode . commands . executeCommand ( 'vscode.executeDefinitionProvider' , uri , pos ) ;
const results = ( locations || [ ] ) . map ( loc => ( {
file : vscode . workspace . asRelativePath ( loc . uri || loc . targetUri , false ) ,
line : ( ( loc . range || loc . targetRange || { } ) . start || { } ) . line + 1 ,
character : ( ( loc . range || loc . targetRange || { } ) . start || { } ) . character
} ) ) ;
webviewView . webview . postMessage ( { type : 'definition-result' , results , id : msg . id } ) ;
} catch ( e ) {
webviewView . webview . postMessage ( { type : 'definition-result' , results : [ ] , error : e . message , id : msg . id } ) ;
}
} else if ( msg . type === 'find-references' ) {
// LSP: Find all references to symbol
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 pos = new vscode . Position ( ( msg . line || 1 ) - 1 , msg . character || 0 ) ;
const locations = await vscode . commands . executeCommand ( 'vscode.executeReferenceProvider' , uri , pos ) ;
const results = ( locations || [ ] ) . slice ( 0 , 50 ) . map ( loc => ( {
file : vscode . workspace . asRelativePath ( loc . uri , false ) ,
line : ( loc . range . start . line || 0 ) + 1 ,
preview : ''
} ) ) ;
webviewView . webview . postMessage ( { type : 'references-result' , results , id : msg . id } ) ;
} catch ( e ) {
webviewView . webview . postMessage ( { type : 'references-result' , results : [ ] , error : e . message , id : msg . id } ) ;
}
} else if ( msg . type === 'document-symbols' ) {
// LSP: Get all symbols in a document (functions, classes, variables)
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 symbols = await vscode . commands . executeCommand ( 'vscode.executeDocumentSymbolProvider' , uri ) ;
const flatten = ( syms , depth ) => {
const out = [ ] ;
for ( const s of ( syms || [ ] ) ) {
out . push ( { name : s . name , kind : vscode . SymbolKind [ s . kind ] || s . kind , line : ( s . range || s . location ? . range ) ? . start ? . line + 1 , detail : s . detail || '' } ) ;
if ( s . children ) out . push ( ... flatten ( s . children , depth + 1 ) ) ;
}
return out ;
} ;
webviewView . webview . postMessage ( { type : 'symbols-result' , symbols : flatten ( symbols , 0 ) . slice ( 0 , 200 ) , id : msg . id } ) ;
} catch ( e ) {
webviewView . webview . postMessage ( { type : 'symbols-result' , symbols : [ ] , error : e . message , id : msg . id } ) ;
}
} else if ( msg . type === 'hover-info' ) {
// LSP: Get hover info (type info, docs) for symbol at position
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 pos = new vscode . Position ( ( msg . line || 1 ) - 1 , msg . character || 0 ) ;
const hovers = await vscode . commands . executeCommand ( 'vscode.executeHoverProvider' , uri , pos ) ;
const contents = ( hovers || [ ] ) . map ( h => ( h . contents || [ ] ) . map ( c => typeof c === 'string' ? c : c . value || '' ) . join ( '\\n' ) ) . join ( '\\n' ) ;
webviewView . webview . postMessage ( { type : 'hover-result' , info : contents , id : msg . id } ) ;
} catch ( e ) {
webviewView . webview . postMessage ( { type : 'hover-result' , info : '' , error : e . message , id : msg . id } ) ;
}
} else if ( msg . type === 'workspace-symbols' ) {
// LSP: Search symbols across workspace by query
try {
const symbols = await vscode . commands . executeCommand ( 'vscode.executeWorkspaceSymbolProvider' , msg . query || '' ) ;
const results = ( symbols || [ ] ) . slice ( 0 , 50 ) . map ( s => ( {
name : s . name , kind : vscode . SymbolKind [ s . kind ] || s . kind ,
file : vscode . workspace . asRelativePath ( s . location . uri , false ) ,
line : ( s . location . range ? . start ? . line || 0 ) + 1
} ) ) ;
webviewView . webview . postMessage ( { type : 'workspace-symbols-result' , symbols : results , id : msg . id } ) ;
} catch ( e ) {
webviewView . webview . postMessage ( { type : 'workspace-symbols-result' , symbols : [ ] , error : e . message , id : msg . id } ) ;
}
} else if ( msg . type === 'get-tool-registry' ) {
webviewView . webview . postMessage ( { type : 'tool-registry' , tools : TOOL _REGISTRY , id : msg . id } ) ;
v2.0.0: Maximum capability extraction — 48 tools, full-stack IDE brain
GIT OPERATIONS TOOLKIT (6 tools):
- git-commit: Stage all + commit with message
- git-diff: Working tree or staged diff (per-file or full)
- git-log: Commit history with count and file filter
- git-blame: Line-by-line authorship (with range support)
- git-stash: push/pop/list/drop operations
- git-branch: list/create/switch/delete branches
CODE REFACTORING (4 tools):
- rename-symbol: LSP-powered rename across all files
- code-actions: Get available quick fixes and refactorings
- apply-code-action: Execute a specific code action by title
- format-document: Auto-format any document
SYSTEM & PROCESS TOOLS (5 tools):
- system-info: CPU, RAM, uptime, node version, load average
- list-ports: All listening TCP ports with processes
- pm2-list: Full PM2 service inventory with CPU/mem/restarts
- disk-usage: Storage for / and /home
- env-vars: Environment variables with filter
DATA FORMAT UTILITIES (6 tools):
- format-json: Pretty-print JSON
- compute-hash: SHA-256/512, MD5, SHA-1
- base64: Encode/decode
- url-encode: Encode/decode
- generate-uuid: Random UUID v4
- count-lines: Lines/words/chars/blanks per file
ADVANCED FILE OPERATIONS (4 tools):
- diff-files: Unified diff of any two files
- find-replace-all: Bulk find/replace with regex support
- recent-files: Recently modified files
- file-sizes: File sizes by glob pattern
TEST RUNNER (2 tools):
- detect-tests: Auto-detect framework (jest/mocha/vitest/pytest/phpunit/cargo/go)
- run-tests: Execute tests with framework selection
CODE ANALYSIS ENGINE (4 tools):
- import-graph: Extract all imports (JS/TS/Python/PHP/Go/Rust)
- analyze-complexity: Functions, nesting depth, loops, conditions, try/catch
- dependency-info: All project deps (Node/Python/PHP Composer)
- project-stats: Language breakdown, file counts, sizes, largest files
ENHANCED DIAGNOSTICS:
- get-diagnostics-detail: Every diagnostic with file, line, severity, message, code, source
SYSTEM PROMPT — Advanced Reasoning Protocol:
- Planning Mode: numbered steps, dependencies, parallel opportunities
- Chain-of-Thought: observe → hypothesize → identify → fix → verify
- Context Synthesis: trace call chains via LSP before recommendations
- Adversarial Review: self-review after implementation
- Full-Stack Debugging Protocol: diagnostics → source → git diff → imports → tests
- System Awareness: AI knows it has 48 tools and how to use them proactively
2026-04-07 12:28:32 -04:00
// ────── GIT OPERATIONS ──────
} else if ( msg . type === 'git-commit' ) {
const result = gitCommit ( msg . message || 'Auto-commit from Alfred IDE' ) ;
webviewView . webview . postMessage ( { type : 'git-result' , action : 'commit' , result , id : msg . id } ) ;
} else if ( msg . type === 'git-diff' ) {
const result = gitDiff ( msg . filePath , msg . staged ) ;
webviewView . webview . postMessage ( { type : 'git-result' , action : 'diff' , result , id : msg . id } ) ;
} else if ( msg . type === 'git-log' ) {
const result = gitLog ( msg . count , msg . filePath ) ;
webviewView . webview . postMessage ( { type : 'git-result' , action : 'log' , result , id : msg . id } ) ;
} else if ( msg . type === 'git-blame' ) {
const result = gitBlame ( msg . filePath , msg . startLine , msg . endLine ) ;
webviewView . webview . postMessage ( { type : 'git-result' , action : 'blame' , result , id : msg . id } ) ;
} else if ( msg . type === 'git-stash' ) {
const result = gitStash ( msg . action || 'push' , msg . message ) ;
webviewView . webview . postMessage ( { type : 'git-result' , action : 'stash' , result , id : msg . id } ) ;
} else if ( msg . type === 'git-branch' ) {
const result = gitBranch ( msg . action || 'list' , msg . branchName ) ;
webviewView . webview . postMessage ( { type : 'git-result' , action : 'branch' , result , id : msg . id } ) ;
// ────── CODE REFACTORING ──────
} else if ( msg . type === 'rename-symbol' ) {
try {
const root = getWorkspaceRoot ( ) ;
const fullPath = path . isAbsolute ( msg . filePath ) ? msg . filePath : path . join ( root , msg . filePath ) ;
const uri = vscode . Uri . file ( fullPath ) ;
await vscode . workspace . openTextDocument ( uri ) ;
const pos = new vscode . Position ( ( msg . line || 1 ) - 1 , msg . character || 0 ) ;
const edit = await vscode . commands . executeCommand ( 'vscode.executeDocumentRenameProvider' , uri , pos , msg . newName || 'renamed' ) ;
if ( edit ) {
await vscode . workspace . applyEdit ( edit ) ;
webviewView . webview . postMessage ( { type : 'refactor-result' , action : 'rename' , success : true , id : msg . id } ) ;
} else {
webviewView . webview . postMessage ( { type : 'refactor-result' , action : 'rename' , success : false , error : 'No rename available at position' , id : msg . id } ) ;
}
} catch ( e ) {
webviewView . webview . postMessage ( { type : 'refactor-result' , action : 'rename' , success : false , error : e . message , id : msg . id } ) ;
}
} else if ( msg . type === 'code-actions' ) {
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 startPos = new vscode . Position ( ( msg . startLine || 1 ) - 1 , 0 ) ;
const endPos = new vscode . Position ( ( msg . endLine || msg . startLine || 1 ) - 1 , 999 ) ;
const range = new vscode . Range ( startPos , endPos ) ;
const actions = await vscode . commands . executeCommand ( 'vscode.executeCodeActionProvider' , uri , range ) ;
const results = ( actions || [ ] ) . slice ( 0 , 20 ) . map ( a => ( {
title : a . title , kind : a . kind ? a . kind . value : '' , isPreferred : a . isPreferred || false
} ) ) ;
webviewView . webview . postMessage ( { type : 'code-actions-result' , actions : results , id : msg . id } ) ;
} catch ( e ) {
webviewView . webview . postMessage ( { type : 'code-actions-result' , actions : [ ] , error : e . message , id : msg . id } ) ;
}
} else if ( msg . type === 'apply-code-action' ) {
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 startPos = new vscode . Position ( ( msg . startLine || 1 ) - 1 , 0 ) ;
const endPos = new vscode . Position ( ( msg . endLine || msg . startLine || 1 ) - 1 , 999 ) ;
const range = new vscode . Range ( startPos , endPos ) ;
const actions = await vscode . commands . executeCommand ( 'vscode.executeCodeActionProvider' , uri , range ) ;
const target = ( actions || [ ] ) . find ( a => a . title === msg . actionTitle ) ;
if ( target ) {
if ( target . edit ) await vscode . workspace . applyEdit ( target . edit ) ;
if ( target . command ) await vscode . commands . executeCommand ( target . command . command , ... ( target . command . arguments || [ ] ) ) ;
webviewView . webview . postMessage ( { type : 'refactor-result' , action : 'code-action' , success : true , title : msg . actionTitle , id : msg . id } ) ;
} else {
webviewView . webview . postMessage ( { type : 'refactor-result' , action : 'code-action' , success : false , error : 'Action not found' , id : msg . id } ) ;
}
} catch ( e ) {
webviewView . webview . postMessage ( { type : 'refactor-result' , action : 'code-action' , success : false , error : e . message , id : msg . id } ) ;
}
} else if ( msg . type === 'format-document' ) {
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 edits = await vscode . commands . executeCommand ( 'vscode.executeFormatDocumentProvider' , uri , { tabSize : 2 , insertSpaces : true } ) ;
if ( edits && edits . length > 0 ) {
const edit = new vscode . WorkspaceEdit ( ) ;
for ( const e of edits ) edit . replace ( uri , e . range , e . newText ) ;
await vscode . workspace . applyEdit ( edit ) ;
webviewView . webview . postMessage ( { type : 'refactor-result' , action : 'format' , success : true , edits : edits . length , id : msg . id } ) ;
} else {
webviewView . webview . postMessage ( { type : 'refactor-result' , action : 'format' , success : true , edits : 0 , id : msg . id } ) ;
}
} catch ( e ) {
webviewView . webview . postMessage ( { type : 'refactor-result' , action : 'format' , success : false , error : e . message , id : msg . id } ) ;
}
// ────── SYSTEM & PROCESS TOOLS ──────
} else if ( msg . type === 'system-info' ) {
webviewView . webview . postMessage ( { type : 'system-info-result' , info : getSystemInfo ( ) , id : msg . id } ) ;
} else if ( msg . type === 'list-ports' ) {
webviewView . webview . postMessage ( { type : 'ports-result' , ports : getRunningPorts ( ) , id : msg . id } ) ;
} else if ( msg . type === 'pm2-list' ) {
try {
const raw = getPm2Services ( ) ;
const services = JSON . parse ( raw || '[]' ) . map ( s => ( {
name : s . name , id : s . pm _id , status : s . pm2 _env ? . status , cpu : s . monit ? . cpu , mem : ( ( s . monit ? . memory || 0 ) / 1048576 ) . toFixed ( 1 ) + 'MB' ,
restarts : s . pm2 _env ? . restart _time , uptime : s . pm2 _env ? . pm _uptime ? new Date ( s . pm2 _env . pm _uptime ) . toISOString ( ) : ''
} ) ) ;
webviewView . webview . postMessage ( { type : 'pm2-result' , services , id : msg . id } ) ;
} catch ( e ) {
webviewView . webview . postMessage ( { type : 'pm2-result' , services : [ ] , error : e . message , id : msg . id } ) ;
}
} else if ( msg . type === 'disk-usage' ) {
webviewView . webview . postMessage ( { type : 'disk-result' , usage : getDiskUsage ( ) , id : msg . id } ) ;
} else if ( msg . type === 'env-vars' ) {
webviewView . webview . postMessage ( { type : 'env-result' , vars : getEnvironmentVars ( msg . filter ) , id : msg . id } ) ;
// ────── DATA FORMAT UTILITIES ──────
} else if ( msg . type === 'format-json' ) {
webviewView . webview . postMessage ( { type : 'format-result' , result : formatJson ( msg . text || '' ) , id : msg . id } ) ;
} else if ( msg . type === 'compute-hash' ) {
webviewView . webview . postMessage ( { type : 'hash-result' , hash : computeHash ( msg . text || '' , msg . algorithm ) , algorithm : msg . algorithm || 'sha256' , id : msg . id } ) ;
} else if ( msg . type === 'base64' ) {
const result = msg . action === 'decode' ? base64Decode ( msg . text || '' ) : base64Encode ( msg . text || '' ) ;
webviewView . webview . postMessage ( { type : 'base64-result' , result , action : msg . action , id : msg . id } ) ;
} else if ( msg . type === 'url-encode' ) {
const result = msg . action === 'decode' ? urlDecode ( msg . text || '' ) : urlEncode ( msg . text || '' ) ;
webviewView . webview . postMessage ( { type : 'url-encode-result' , result , action : msg . action , id : msg . id } ) ;
} else if ( msg . type === 'generate-uuid' ) {
webviewView . webview . postMessage ( { type : 'uuid-result' , uuid : generateUuid ( ) , id : msg . id } ) ;
} else if ( msg . type === 'count-lines' ) {
webviewView . webview . postMessage ( { type : 'count-result' , stats : countLines ( msg . filePath || '' ) , id : msg . id } ) ;
// ────── ADVANCED FILE OPS ──────
} else if ( msg . type === 'diff-files' ) {
webviewView . webview . postMessage ( { type : 'diff-result' , diff : diffFiles ( msg . fileA , msg . fileB ) , id : msg . id } ) ;
} else if ( msg . type === 'find-replace-all' ) {
const result = findReplace ( msg . filePath , msg . search , msg . replace , msg . isRegex ) ;
webviewView . webview . postMessage ( { type : 'find-replace-result' , result , id : msg . id } ) ;
} else if ( msg . type === 'recent-files' ) {
webviewView . webview . postMessage ( { type : 'recent-files-result' , files : getRecentFiles ( msg . count ) , id : msg . id } ) ;
} else if ( msg . type === 'file-sizes' ) {
webviewView . webview . postMessage ( { type : 'file-sizes-result' , sizes : getFileSizes ( msg . pattern ) , id : msg . id } ) ;
// ────── TEST RUNNER ──────
} else if ( msg . type === 'detect-tests' ) {
webviewView . webview . postMessage ( { type : 'test-frameworks' , frameworks : detectTestFramework ( ) , id : msg . id } ) ;
} else if ( msg . type === 'run-tests' ) {
const result = runTests ( msg . framework , msg . testFile ) ;
webviewView . webview . postMessage ( { type : 'test-result' , result , id : msg . id } ) ;
// ────── ANALYSIS TOOLS ──────
} else if ( msg . type === 'import-graph' ) {
webviewView . webview . postMessage ( { type : 'import-graph-result' , graph : getImportGraph ( msg . filePath ) , id : msg . id } ) ;
} else if ( msg . type === 'analyze-complexity' ) {
webviewView . webview . postMessage ( { type : 'complexity-result' , analysis : analyzeComplexity ( msg . filePath ) , id : msg . id } ) ;
} else if ( msg . type === 'dependency-info' ) {
webviewView . webview . postMessage ( { type : 'dependency-result' , info : getDependencyInfo ( ) , id : msg . id } ) ;
} else if ( msg . type === 'project-stats' ) {
webviewView . webview . postMessage ( { type : 'project-stats-result' , stats : getProjectStats ( ) , id : msg . id } ) ;
} else if ( msg . type === 'get-diagnostics-detail' ) {
const diags = vscode . languages . getDiagnostics ( ) ;
const detailed = [ ] ;
for ( const [ uri , items ] of diags ) {
for ( const d of items ) {
detailed . push ( {
file : vscode . workspace . asRelativePath ( uri , false ) ,
line : d . range . start . line + 1 , col : d . range . start . character ,
severity : d . severity === 0 ? 'error' : d . severity === 1 ? 'warning' : d . severity === 2 ? 'info' : 'hint' ,
message : d . message , source : d . source || '' , code : d . code ? ( typeof d . code === 'object' ? d . code . value : d . code ) : ''
} ) ;
}
}
webviewView . webview . postMessage ( { type : 'diagnostics-detail-result' , diagnostics : detailed . slice ( 0 , 100 ) , id : msg . id } ) ;
2026-04-07 11:40:25 -04:00
}
} ) ;
if ( userProfile ) {
setTimeout ( ( ) => {
webviewView . webview . postMessage ( { type : 'user-profile' , profile : userProfile } ) ;
} , 500 ) ;
}
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
2026-04-07 12:11:42 -04:00
// 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 } ) ;
}
} ) ;
2026-04-07 11:40:25 -04:00
}
}
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 \n Content-Disposition: form-data; name="audio"; filename=" ${ filename } " \r \n Content-Type: ${ contentType } \r \n \r \n `
) ;
const modelField = Buffer . from (
` \r \n -- ${ boundary } \r \n Content-Disposition: form-data; name="model" \r \n \r \n whisper-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 ( ) {
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
2026-04-07 12:11:42 -04:00
return buildFullContext ( ) ;
2026-04-07 11:40:25 -04:00
}
async function queryAlfredAPI ( prompt , agent , editorContext , selectedModel = 'sonnet' , images = [ ] , pdfFiles = [ ] , attachmentTexts = [ ] , zipFiles = [ ] , multiplier = 30 ) {
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
2026-04-07 12:11:42 -04:00
const systemPrompt = buildSystemPrompt ( userProfile ) ;
2026-04-07 11:40:25 -04:00
const payload = {
message : prompt , agent : agent || 'alfred' ,
context : editorContext || '' , channel : 'ide-chat' ,
conv _id : convId || '' , model : selectedModel ,
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
2026-04-07 12:11:42 -04:00
token _multiplier : multiplier || 30 ,
system _prompt : systemPrompt
2026-04-07 11:40:25 -04:00
} ;
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 ` <!DOCTYPE html><html><head><meta charset="UTF-8">
< style > body { font - family : 'Segoe UI' , system - ui , sans - serif ; background : # 0 d1117 ; color : # c9d1d9 ; display : flex ; align - items : center ; justify - content : center ; height : 100 vh ; margin : 0 ; }
. loader { text - align : center ; } . spin { display : inline - block ; width : 40 px ; height : 40 px ; border : 3 px solid # 30363 d ; border - top - color : # e2b340 ; border - radius : 50 % ; animation : spin 0.8 s linear infinite ; }
@ keyframes spin { to { transform : rotate ( 360 deg ) ; } } < / s t y l e > < / h e a d >
< body > < div class = "loader" > < div class = "spin" > < / d i v > < p s t y l e = " m a r g i n - t o p : 1 6 p x ; c o l o r : # 8 b 9 4 9 e ; " > L o a d i n g y o u r s t a t s . . . < / p > < / d i v > < / b o d y > < / h t m l > ` ;
}
if ( ! stats . valid ) {
return ` <!DOCTYPE html><html><head><meta charset="UTF-8">
< style > body { font - family : 'Segoe UI' , system - ui , sans - serif ; background : # 0 d1117 ; color : # c9d1d9 ; padding : 40 px ; margin : 0 ; } < / s t y l e > < / h e a d >
< body > < h2 style = "color:#e2b340;" > Account Stats < / h 2 > < p s t y l e = " c o l o r : # f 8 5 1 4 9 ; m a r g i n - t o p : 1 6 p x ; " > ` + e s c H t m l ( s t a t s . e r r o r | | ' U n a b l e t o l o a d s t a t s ' ) + ` < / p > < / b o d y > < / h t m l > ` ;
}
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 =>
` <tr><td> ${ escHtml ( s . product || 'Service' ) } </td><td> ${ escHtml ( s . domain || '—' ) } </td><td class="status-active"> ${ escHtml ( s . status ) } </td><td> $ ${ parseFloat ( s . amount || 0 ) . toFixed ( 2 ) } / ${ escHtml ( s . billing _cycle || 'N/A' ) } </td></tr> `
) . join ( '' ) ;
} else {
servicesHtml = '<tr><td colspan="4" style="color:#8b949e;text-align:center;">No active services</td></tr>' ;
}
// Recent invoices
let invoicesHtml = '' ;
if ( stats . recent _invoices && stats . recent _invoices . length > 0 ) {
invoicesHtml = stats . recent _invoices . map ( i => {
const statusClass = i . status === 'Paid' ? 'status-active' : i . status === 'Unpaid' ? 'status-unpaid' : 'status-other' ;
return ` <tr><td># ${ escHtml ( i . invoice _number || String ( i . id ) ) } </td><td> $ ${ parseFloat ( i . total || 0 ) . toFixed ( 2 ) } </td><td class=" ${ statusClass } "> ${ escHtml ( i . status ) } </td><td> ${ escHtml ( ( i . due _date || i . created _at || '' ) . substring ( 0 , 10 ) ) } </td></tr> ` ;
} ) . join ( '' ) ;
} else {
invoicesHtml = '<tr><td colspan="4" style="color:#8b949e;text-align:center;">No invoices</td></tr>' ;
}
// Usage breakdown
let usageBreakdownHtml = '' ;
if ( stats . usage _breakdown && stats . usage _breakdown . length > 0 ) {
usageBreakdownHtml = stats . usage _breakdown . map ( u =>
` <tr><td> ${ escHtml ( u . feature ) } </td><td> ${ escHtml ( u . model ) } </td><td> ${ fmtK ( parseInt ( u . input _tokens || 0 ) + parseInt ( u . output _tokens || 0 ) ) } </td><td> ${ parseInt ( u . requests || 0 ) } </td><td> $ ${ parseFloat ( u . cost _usd || 0 ) . toFixed ( 4 ) } </td></tr> `
) . join ( '' ) ;
} else {
usageBreakdownHtml = '<tr><td colspan="5" style="color:#8b949e;text-align:center;">No usage this period</td></tr>' ;
}
return ` <!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< style >
* { margin : 0 ; padding : 0 ; box - sizing : border - box ; }
body { font - family : 'Segoe UI' , system - ui , sans - serif ; background : # 0 d1117 ; color : # c9d1d9 ; padding : 32 px ; max - width : 900 px ; margin : 0 auto ; }
h1 { color : # e2b340 ; font - size : 22 px ; font - weight : 600 ; margin - bottom : 4 px ; }
. subtitle { color : # 8 b949e ; font - size : 13 px ; margin - bottom : 24 px ; }
. cards { display : grid ; grid - template - columns : repeat ( auto - fit , minmax ( 180 px , 1 fr ) ) ; gap : 16 px ; margin - bottom : 32 px ; }
. card { background : # 161 b22 ; border : 1 px solid # 30363 d ; border - radius : 12 px ; padding : 20 px ; }
. card - label { font - size : 11 px ; color : # 8 b949e ; text - transform : uppercase ; letter - spacing : 0.5 px ; margin - bottom : 6 px ; }
. card - value { font - size : 24 px ; font - weight : 700 ; }
. card - sub { font - size : 11 px ; color : # 8 b949e ; margin - top : 4 px ; }
. usage - section { background : # 161 b22 ; border : 1 px solid # 30363 d ; border - radius : 12 px ; padding : 20 px ; margin - bottom : 24 px ; }
. usage - header { display : flex ; justify - content : space - between ; align - items : center ; margin - bottom : 12 px ; }
. usage - title { font - size : 14 px ; font - weight : 600 ; }
. plan - badge { display : inline - block ; padding : 3 px 10 px ; border - radius : 12 px ; font - size : 10 px ; font - weight : 700 ; text - transform : uppercase ; letter - spacing : 0.5 px ; }
. usage - bar - outer { height : 8 px ; border - radius : 99 px ; background : # 21262 d ; overflow : hidden ; margin - bottom : 8 px ; }
. usage - bar - fill { height : 100 % ; border - radius : 99 px ; transition : width 0.5 s ease ; }
. usage - detail { font - size : 12 px ; color : # 8 b949e ; }
. section - title { font - size : 15 px ; font - weight : 600 ; color : # e2b340 ; margin - bottom : 12 px ; margin - top : 8 px ; }
table { width : 100 % ; border - collapse : collapse ; margin - bottom : 24 px ; }
th { text - align : left ; font - size : 11 px ; color : # 8 b949e ; text - transform : uppercase ; letter - spacing : 0.3 px ; padding : 8 px 12 px ; border - bottom : 1 px solid # 30363 d ; }
td { padding : 10 px 12 px ; border - bottom : 1 px solid # 21262 d ; font - size : 13 px ; }
tr : hover td { background : rgba ( 226 , 179 , 64 , 0.04 ) ; }
. status - active { color : # 3 fb950 ; font - weight : 600 ; }
. status - unpaid { color : # f85149 ; font - weight : 600 ; }
. status - other { color : # 8 b949e ; }
. refresh - btn { background : # 161 b22 ; border : 1 px solid # 30363 d ; color : # c9d1d9 ; padding : 8 px 16 px ; border - radius : 8 px ; cursor : pointer ; font - size : 12 px ; float : right ; }
. refresh - btn : hover { border - color : # e2b340 ; color : # e2b340 ; }
. footer { text - align : center ; color : # 484 f58 ; font - size : 11 px ; margin - top : 24 px ; padding - top : 16 px ; border - top : 1 px solid # 21262 d ; }
< / s t y l e >
< / h e a d >
< body >
< h1 > $ ( account ) $ { name } < / h 1 >
< div class = "subtitle" > $ { email } · Last login : $ { escHtml ( ( stats . last _login || '' ) . substring ( 0 , 16 ) ) } < / d i v >
< div class = "cards" >
< div class = "card" >
< div class = "card-label" > Plan < / d i v >
< div class = "card-value" style = "color:${planColor}" > $ { planDisplay } < / d i v >
< / d i v >
< div class = "card" >
< div class = "card-label" > Credit Balance < / d i v >
< div class = "card-value" style = "color:#3fb950" > $$ { escHtml ( stats . credit _balance || '0.00' ) } < / d i v >
< / d i v >
< div class = "card" >
< div class = "card-label" > Active Services < / d i v >
< div class = "card-value" > $ { stats . active _services || 0 } < / d i v >
< / d i v >
< div class = "card" >
< div class = "card-label" > Unpaid Invoices < / d i v >
< div class = "card-value" style = "color:${(stats.unpaid_invoices || 0) > 0 ? '#f85149' : '#3fb950'}" > $ { stats . unpaid _invoices || 0 } < / d i v >
< div class = "card-sub" > $ { ( stats . unpaid _invoices || 0 ) > 0 ? 'Total due: $' + escHtml ( stats . total _due || '0.00' ) : 'All clear' } < / d i v >
< / d i v >
< / d i v >
< div class = "usage-section" >
< div class = "usage-header" >
< span class = "usage-title" > Token Usage — $ { new Date ( ) . toLocaleString ( 'en-US' , { month : 'long' , year : 'numeric' } ) } < / s p a n >
< span class = "plan-badge" style = "background:${planColor}20;color:${planColor};border:1px solid ${planColor}40" > $ { planDisplay } < / s p a n >
< / d i v >
$ { isUnlimited
? ` <div class="usage-bar-outer"><div class="usage-bar-fill" style="width:100%;background:#e2b340;"></div></div>
< div class = "usage-detail" > Unlimited usage — Commander plan < / d i v > `
: ` <div class="usage-bar-outer"><div class="usage-bar-fill" style="width: ${ pct } %;background: ${ barColor } ;"></div></div>
< div class = "usage-detail" > $ { fmtK ( tokensUsed ) } used of $ { fmtK ( tokensIncluded ) } included ( $ { fmtK ( remaining ) } remaining ) $ { tokensOverage > 0 ? ' · Overage: ' + fmtK ( tokensOverage ) + ' tokens ($' + costOverage . toFixed ( 2 ) + ')' : '' } < / d i v > `
}
< / d i v >
< div class = "section-title" > Usage Breakdown < / d i v >
< table >
< thead > < tr > < th > Feature < / t h > < t h > M o d e l < / t h > < t h > T o k e n s < / t h > < t h > R e q u e s t s < / t h > < t h > C o s t < / t h > < / t r > < / t h e a d >
< tbody > $ { usageBreakdownHtml } < / t b o d y >
< / t a b l e >
< div class = "section-title" > Active Services < / d i v >
< table >
< thead > < tr > < th > Product < / t h > < t h > D o m a i n < / t h > < t h > S t a t u s < / t h > < t h > A m o u n t < / t h > < / t r > < / t h e a d >
< tbody > $ { servicesHtml } < / t b o d y >
< / t a b l e >
< div class = "section-title" > Recent Invoices < / d i v >
< table >
< thead > < tr > < th > Invoice < / t h > < t h > A m o u n t < / t h > < t h > S t a t u s < / t h > < t h > D a t e < / t h > < / t r > < / t h e a d >
< tbody > $ { invoicesHtml } < / t b o d y >
< / t a b l e >
< div class = "footer" > Alfred · GoSiteMe · Data refreshed at $ { new Date ( ) . toLocaleTimeString ( ) } < / d i v >
< / b o d y >
< / h t m l > ` ;
}
function escHtml ( str ) {
return String ( str || '' ) . replace ( /&/g , '&' ) . replace ( /</g , '<' ) . replace ( />/g , '>' ) . replace ( /"/g , '"' ) ;
}
function getWebviewContent ( injectedToken , injectedIdentity ) {
const safeToken = ( injectedToken || '' ) . replace ( /[^a-f0-9]/g , '' ) ;
const safeIdentity = JSON . stringify ( injectedIdentity || { } ) ;
return ` <!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta http - equiv = "Content-Security-Policy" content = "default-src 'self' 'unsafe-inline' 'unsafe-eval' https: wss: ws: data: blob:; connect-src 'self' https://gositeme.com https: wss: ws:; img-src 'self' data: blob: https:; font-src 'self' data: blob: https:; style-src 'self' 'unsafe-inline';" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< style >
* { margin : 0 ; padding : 0 ; box - sizing : border - box ; }
body { font - family : 'Segoe UI' , system - ui , sans - serif ; background : var ( -- vscode - editor - background , # 0 d1117 ) ; color : var ( -- vscode - editor - foreground , # c9d1d9 ) ; padding : 10 px ; height : 100 vh ; display : flex ; flex - direction : column ; }
. header { text - align : center ; padding : 6 px 0 10 px ; border - bottom : 1 px solid var ( -- vscode - panel - border , # 1 a1a2e ) ; margin - bottom : 8 px ; }
. header h2 { color : # e2b340 ; font - size : 13 px ; font - weight : 600 ; letter - spacing : 1 px ; text - transform : uppercase ; }
. header . subtitle { color : var ( -- vscode - descriptionForeground , # 6 a737d ) ; font - size : 10 px ; margin - top : 2 px ; }
. identity - badge { margin - top : 4 px ; display : inline - block ; font - size : 10 px ; padding : 2 px 8 px ; border - radius : 999 px ; border : 1 px solid var ( -- vscode - input - border , # 30363 d ) ; color : var ( -- vscode - descriptionForeground , # 8 b949e ) ; }
. identity - badge . verified { color : # 0 d1117 ; background : # 22 c55e ; border - color : # 22 c55e ; }
. identity - badge . guest { color : # f59e0b ; border - color : # f59e0b ; }
. usage - info { display : none ; font - size : 10 px ; margin - top : 4 px ; color : var ( -- vscode - descriptionForeground , # 8 b949e ) ; text - align : center ; }
. usage - info . plan - label { font - weight : 600 ; text - transform : uppercase ; letter - spacing : 0.4 px ; font - size : 9 px ; }
. usage - info . plan - commander { color : # e2b340 ; }
. usage - info . plan - free { color : # 6 a737d ; }
. usage - info . plan - starter { color : # 3 b82f6 ; }
. usage - info . plan - professional { color : # a855f7 ; }
. usage - info . plan - enterprise { color : # 22 c55e ; }
. usage - bar { height : 4 px ; border - radius : 99 px ; background : rgba ( 255 , 255 , 255 , 0.08 ) ; overflow : hidden ; margin - top : 3 px ; width : 100 % ; }
. usage - fill { height : 100 % ; border - radius : 99 px ; background : # 3 b82f6 ; transition : width 0.3 s ease ; }
. usage - fill . warn { background : # f59e0b ; }
. usage - fill . danger { background : # ef4444 ; }
. usage - fill . unlimited { background : # e2b340 ; width : 100 % ! important ; }
/* One compact row; wraps on narrow sidebars without extra vertical chrome */
. agent - bar { display : flex ; flex - wrap : wrap ; align - items : center ; gap : 6 px ; margin - bottom : 6 px ; }
. agent - bar > label { font - size : 9 px ; color : var ( -- vscode - descriptionForeground ) ; text - transform : uppercase ; letter - spacing : 0.4 px ; flex - shrink : 0 ; }
. agent - select , . model - select {
flex : 1 1 42 % ; min - width : 0 ; min - height : 26 px ;
background : var ( -- vscode - input - background , # 161 b22 ) ; border : 1 px solid var ( -- vscode - input - border , # 30363 d ) ; border - radius : 6 px ;
color : var ( -- vscode - input - foreground , # c9d1d9 ) ; padding : 4 px 6 px ; font - size : 11 px ; outline : none ; cursor : pointer ;
}
. model - select { color : # 3 b82f6 ; }
. multiplier - select { flex : 0 0 92 px ; color : # e2b340 ; }
. agent - select : focus { border - color : # e2b340 ; }
. model - select : focus { border - color : # 3 b82f6 ; }
. model - select option , . model - select optgroup { background : # 161 b22 ; color : # c9d1d9 ; }
. chat - area { flex : 1 ; overflow - y : auto ; margin - bottom : 8 px ; padding - right : 4 px ; }
. chat - area : : - webkit - scrollbar { width : 4 px ; }
. chat - area : : - webkit - scrollbar - thumb { background : # 1 a1a2e ; border - radius : 2 px ; }
. message { margin - bottom : 8 px ; padding : 7 px 9 px ; border - radius : 8 px ; font - size : 12 px ; line - height : 1.5 ; animation : fadeIn 0.3 s ease ; word - wrap : break - word ; }
@ keyframes fadeIn { from { opacity : 0 ; transform : translateY ( 4 px ) ; } to { opacity : 1 ; transform : translateY ( 0 ) ; } }
. message . commander { background : rgba ( 226 , 179 , 64 , 0.08 ) ; border - left : 3 px solid # e2b340 ; }
. message . alfred { background : rgba ( 59 , 130 , 246 , 0.08 ) ; border - left : 3 px solid # 3 b82f6 ; }
. message . system { background : rgba ( 35 , 134 , 54 , 0.08 ) ; border - left : 3 px solid # 238636 ; color : # 7 ee787 ; font - size : 11 px ; padding : 5 px 8 px ; }
. message . sender { font - size : 9 px ; font - weight : 700 ; text - transform : uppercase ; letter - spacing : 0.5 px ; margin - bottom : 2 px ; }
. message . commander . sender { color : # e2b340 ; }
. message . alfred . sender { color : # 3 b82f6 ; }
. message pre { background : var ( -- vscode - textCodeBlock - background , # 0 d1117 ) ; border : 1 px solid var ( -- vscode - panel - border , # 30363 d ) ; border - radius : 4 px ; padding : 6 px 8 px ; margin : 4 px 0 ; overflow - x : auto ; font - size : 11 px ; font - family : var ( -- vscode - editor - font - family , monospace ) ; }
. message code { background : rgba ( 255 , 255 , 255 , 0.05 ) ; padding : 1 px 4 px ; border - radius : 3 px ; font - size : 11 px ; }
/* Input full width, then a short action row — avoids cramming mic + attach + send beside the field */
. controls - wrap { display : flex ; flex - direction : column ; gap : 6 px ; width : 100 % ; min - width : 0 ; }
. controls - wrap . text - input { width : 100 % ; flex : none ; min - width : 0 ; }
. action - row { display : flex ; align - items : center ; gap : 8 px ; width : 100 % ; min - width : 0 ; flex - wrap : nowrap ; }
. action - row . mic - btn { flex - shrink : 0 ; }
. action - row . attach - btn { flex - shrink : 0 ; }
. action - row . send - btn { margin - left : auto ; flex - shrink : 0 ; }
. mic - btn { width : 32 px ; height : 32 px ; border - radius : 50 % ; border : 2 px solid # e2b340 ; background : transparent ; color : # e2b340 ; cursor : pointer ; display : flex ; align - items : center ; justify - content : center ; transition : all 0.3 s ; flex - shrink : 0 ; }
. mic - btn : hover { background : # e2b340 ; color : # 0 d1117 ; }
. mic - btn . recording { background : # e53e3e ; border - color : # e53e3e ; color : # fff ; animation : pulse 1.5 s infinite ; }
. mic - btn . transcribing { background : # 3 b82f6 ; border - color : # 3 b82f6 ; color : # fff ; animation : pulse 1 s infinite ; }
@ keyframes pulse { 0 % , 100 % { box - shadow : 0 0 8 px rgba ( 226 , 179 , 64 , 0.3 ) ; } 50 % { box - shadow : 0 0 20 px rgba ( 226 , 179 , 64 , 0.6 ) ; } }
. mic - btn svg { width : 16 px ; height : 16 px ; }
. text - input { background : var ( -- vscode - input - background ) ; border : 1 px solid var ( -- vscode - input - border , # 30363 d ) ; border - radius : 8 px ; color : var ( -- vscode - input - foreground ) ; padding : 7 px 10 px ; font - size : 12 px ; outline : none ; }
. text - input : focus { border - color : # e2b340 ; }
. text - input : : placeholder { color : var ( -- vscode - input - placeholderForeground , # 484 f58 ) ; }
. send - btn { background : # e2b340 ; border : none ; color : # 0 d1117 ; border - radius : 8 px ; padding : 6 px 14 px ; cursor : pointer ; font - weight : 700 ; font - size : 11 px ; }
. send - btn : hover { opacity : 0.85 ; }
. bottom - bar { display : flex ; align - items : center ; justify - content : flex - start ; flex - wrap : wrap ; margin - top : 4 px ; gap : 6 px ; row - gap : 4 px ; position : relative ; z - index : 5 ; pointer - events : auto ; }
. attach - btn { width : 28 px ; height : 28 px ; border - radius : 6 px ; border : 1 px solid var ( -- vscode - input - border , # 30363 d ) ; background : transparent ; color : var ( -- vscode - descriptionForeground , # 6 a737d ) ; cursor : pointer ; display : flex ; align - items : center ; justify - content : center ; flex - shrink : 0 ; font - size : 14 px ; }
. attach - btn : hover { border - color : # e2b340 ; color : # e2b340 ; }
. image - preview - strip { display : flex ; gap : 4 px ; flex - wrap : wrap ; margin - bottom : 4 px ; }
. image - preview - strip . empty { display : none ; }
. attach - panel { border : 1 px solid var ( -- vscode - input - border , # 30363 d ) ; border - radius : 6 px ; padding : 6 px ; margin - bottom : 6 px ; background : rgba ( 255 , 255 , 255 , 0.02 ) ; }
. attach - panel . empty { display : none ; }
. attach - row { display : flex ; align - items : center ; gap : 6 px ; margin - bottom : 4 px ; font - size : 11 px ; }
. attach - row : last - child { margin - bottom : 0 ; }
. attach - name { flex : 1 ; white - space : nowrap ; overflow : hidden ; text - overflow : ellipsis ; }
. attach - meta { color : var ( -- vscode - descriptionForeground , # 6 a737d ) ; font - size : 10 px ; }
. attach - state { border : 1 px solid var ( -- vscode - input - border , # 30363 d ) ; border - radius : 999 px ; padding : 1 px 6 px ; font - size : 9 px ; color : var ( -- vscode - descriptionForeground , # 8 b949e ) ; white - space : nowrap ; }
. attach - state . ready { color : # 60 a5fa ; border - color : # 60 a5fa ; }
. attach - state . queued { color : # f59e0b ; border - color : # f59e0b ; }
. attach - state . ok { color : # 22 c55e ; border - color : # 22 c55e ; }
. attach - state . warn { color : # f59e0b ; border - color : # f59e0b ; }
. attach - state . error { color : # ef4444 ; border - color : # ef4444 ; }
. attach - detail { color : var ( -- vscode - descriptionForeground , # 6 a737d ) ; font - size : 9 px ; margin - left : 2 px ; max - width : 150 px ; white - space : nowrap ; overflow : hidden ; text - overflow : ellipsis ; }
. small - btn { border : 1 px solid var ( -- vscode - input - border , # 30363 d ) ; background : transparent ; color : var ( -- vscode - foreground , # c9d1d9 ) ; border - radius : 4 px ; cursor : pointer ; font - size : 10 px ; padding : 1 px 5 px ; }
. small - btn : hover { border - color : # e2b340 ; color : # e2b340 ; }
. retry - btn { margin - top : 6 px ; }
. meter { font - size : 9 px ; color : var ( -- vscode - descriptionForeground , # 6 a737d ) ; margin - top : 2 px ; }
. meter . bar { height : 4 px ; border - radius : 999 px ; background : rgba ( 255 , 255 , 255 , 0.08 ) ; overflow : hidden ; margin - top : 2 px ; }
. meter . fill { height : 100 % ; width : 0 % ; background : # 3 b82f6 ; transition : width . 2 s ease ; }
. telemetry - drawer { margin - top : 6 px ; border : 1 px solid var ( -- vscode - input - border , # 30363 d ) ; border - radius : 6 px ; max - height : 120 px ; overflow : auto ; padding : 6 px ; font - size : 10 px ; background : rgba ( 0 , 0 , 0 , 0.15 ) ; }
. telemetry - drawer . hidden { display : none ; }
. telemetry - line { margin - bottom : 3 px ; color : var ( -- vscode - descriptionForeground , # 8 b949e ) ; }
. img - thumb { position : relative ; width : 48 px ; height : 48 px ; border - radius : 4 px ; overflow : hidden ; border : 1 px solid var ( -- vscode - input - border , # 30363 d ) ; flex - shrink : 0 ; }
. img - thumb img { width : 100 % ; height : 100 % ; object - fit : cover ; }
. img - thumb . remove - img { position : absolute ; top : 0 ; right : 0 ; background : rgba ( 0 , 0 , 0 , 0.7 ) ; color : # fff ; border : none ; cursor : pointer ; font - size : 10 px ; padding : 1 px 3 px ; line - height : 1 ; }
. status { font - size : 10 px ; color : var ( -- vscode - descriptionForeground , # 484 f58 ) ; flex : 1 ; }
. status . active { color : # e2b340 ; }
. ctrl - btn { background : none ; border : 1 px solid var ( -- vscode - input - border , # 30363 d ) ; border - radius : 4 px ; font - size : 9 px ; padding : 2 px 6 px ; cursor : pointer ; font - family : inherit ; white - space : nowrap ; }
. ctrl - btn . voice - btn { color : # e2b340 ; }
. ctrl - btn . voice - btn . off { color : # 484 f58 ; }
. ctrl - btn . hf - btn { color : # 484 f58 ; }
. ctrl - btn . hf - btn . active { color : # 0 d1117 ; background : # 22 c55e ; border - color : # 22 c55e ; animation : hfPulse 2 s infinite ; }
@ keyframes hfPulse { 0 % , 100 % { box - shadow : 0 0 4 px rgba ( 34 , 197 , 94 , 0.3 ) ; } 50 % { box - shadow : 0 0 12 px rgba ( 34 , 197 , 94 , 0.6 ) ; } }
. thinking { display : inline - block ; color : # 3 b82f6 ; }
. thinking : : after { content : '' ; animation : dots 1.5 s infinite ; }
@ keyframes dots { 0 % { content : '.' ; } 33 % { content : '..' ; } 66 % { content : '...' ; } }
. ide - quick - bar { display : flex ; flex - wrap : wrap ; gap : 6 px ; align - items : center ; margin - bottom : 8 px ; padding : 8 px 10 px ; border : 1 px solid var ( -- vscode - input - border , # 30363 d ) ; border - radius : 8 px ; background : rgba ( 226 , 179 , 64 , 0.07 ) ; }
. ide - quick - label { font - size : 9 px ; color : var ( -- vscode - descriptionForeground ) ; text - transform : uppercase ; letter - spacing : 0.5 px ; margin - right : 4 px ; flex - shrink : 0 ; }
. ide - q - btn { font - size : 10 px ; padding : 5 px 10 px ; border - radius : 6 px ; border : 1 px solid var ( -- vscode - input - border , # 30363 d ) ; background : var ( -- vscode - input - background , # 161 b22 ) ; color : var ( -- vscode - foreground ) ; cursor : pointer ; white - space : nowrap ; }
. ide - q - btn : hover { border - color : # e2b340 ; color : # e2b340 ; }
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
2026-04-07 12:11:42 -04:00
. code - block - wrap { margin : 6 px 0 ; border : 1 px solid var ( -- vscode - panel - border , # 30363 d ) ; border - radius : 6 px ; overflow : hidden ; }
. code - block - header { display : flex ; align - items : center ; gap : 4 px ; padding : 4 px 8 px ; background : rgba ( 255 , 255 , 255 , 0.03 ) ; border - bottom : 1 px solid var ( -- vscode - panel - border , # 30363 d ) ; }
. code - lang { font - size : 9 px ; color : # 8 b949e ; text - transform : uppercase ; letter - spacing : 0.5 px ; flex : 1 ; }
. cb - btn { font - size : 9 px ; padding : 2 px 8 px ; border - radius : 4 px ; border : 1 px solid var ( -- vscode - input - border , # 30363 d ) ; background : transparent ; cursor : pointer ; font - family : inherit ; }
. cb - copy { color : # 60 a5fa ; }
. cb - copy : hover { background : rgba ( 96 , 165 , 250 , 0.15 ) ; border - color : # 60 a5fa ; }
. cb - insert { color : # 22 c55e ; }
. cb - insert : hover { background : rgba ( 34 , 197 , 94 , 0.15 ) ; border - color : # 22 c55e ; }
. cb - run { color : # e2b340 ; }
. cb - run : hover { background : rgba ( 226 , 179 , 64 , 0.15 ) ; border - color : # e2b340 ; }
2026-04-07 11:40:25 -04:00
< / s t y l e >
< / h e a d >
< body >
< div class = "header" >
< h2 > Alfred < / h 2 >
< div class = "subtitle" > All models · Voice STT / TTS · Attachments · Ctrl + Shift + Alt + A · Enter sends < / d i v >
< div class = "identity-badge" id = "identityBadge" style = "display:none" > Identity < / d i v >
< div class = "usage-info" id = "usageInfo" >
< span class = "plan-label" id = "planLabel" > Plan < / s p a n > · < s p a n i d = " u s a g e T e x t " > L o a d i n g . . . < / s p a n >
< div class = "usage-bar" > < div class = "usage-fill" id = "usageFill" > < / d i v > < / d i v >
< / d i v >
< / d i v >
< div class = "agent-bar" >
< label for = "agentSelect" > Agent < / l a b e l >
< select class = "agent-select" id = "agentSelect" title = "Routing persona — applies on Send" >
< option value = "alfred" selected > Alfred ( General ) < / o p t i o n >
< option value = "cipher" > Cipher ( Security ) < / o p t i o n >
< option value = "sage" > Sage ( Knowledge ) < / o p t i o n >
< option value = "nova" > Nova ( Creative ) < / o p t i o n >
< option value = "atlas" > Atlas ( Strategy ) < / o p t i o n >
< option value = "architect" > Architect ( Engineering ) < / o p t i o n >
< option value = "sentinel" > Sentinel ( Monitoring ) < / o p t i o n >
< option value = "catalyst" > Catalyst ( Growth ) < / o p t i o n >
< option value = "oracle" > Oracle ( Analytics ) < / o p t i o n >
< option value = "scout" > Scout ( Research ) < / o p t i o n >
< / s e l e c t >
< label for = "modelSelect" > Model < / l a b e l >
< select class = "model-select" id = "modelSelect" title = "LLM — applies on Send" >
< optgroup label = "💜 Anthropic" >
< option value = "sonnet" selected > 💜 Sonnet 4.6 < / o p t i o n >
< option value = "haiku" > ⚡ Haiku 4.5 ( Fast ) < / o p t i o n >
< option value = "opus" > 👑 Opus 4.6 ( Max ) < / o p t i o n >
< / o p t g r o u p >
< optgroup label = "🟢 OpenAI" >
< option value = "gpt-4.1" > 🟢 GPT - 4.1 < / o p t i o n >
< option value = "gpt-4.1-mini" > 🟡 GPT - 4.1 Mini < / o p t i o n >
< option value = "gpt-4.1-nano" > 🟤 GPT - 4.1 Nano < / o p t i o n >
< option value = "gpt-4o" > 🔵 GPT - 4 o < / o p t i o n >
< option value = "gpt-4o-mini" > 🔹 GPT - 4 o Mini < / o p t i o n >
< / o p t g r o u p >
< optgroup label = "💎 Google" >
< option value = "gemini-3.1-pro" > 🌐 Gemini 3.1 Pro < / o p t i o n >
< option value = "gemini-3-flash" > ⚡ Gemini 3 Flash < / o p t i o n >
< option value = "gemini-2.5-pro" > 💎 Gemini 2.5 Pro < / o p t i o n >
< option value = "gemini-2.5-flash" > 💡 Gemini 2.5 Flash < / o p t i o n >
< / o p t g r o u p >
< optgroup label = "🔧 Open Source" >
< option value = "turbo" > 🔧 Qwen3 Coder ( Cheap ) < / o p t i o n >
< option value = "deepseek-v3.1" > 🌊 DeepSeek V3 . 1 < / o p t i o n >
< option value = "deepseek-r1" > 🧪 DeepSeek R1 ( Reasoning ) < / o p t i o n >
< option value = "llama-4-maverick" > 🦙 Llama 4 Maverick < / o p t i o n >
< option value = "llama-4-scout" > 🔭 Llama 4 Scout < / o p t i o n >
< option value = "qwen3-coder-480b" > 🏗 ️ Qwen3 Coder 480 B < / o p t i o n >
< option value = "mistral-small" > 🇫 🇷 Mistral Small < / o p t i o n >
< / o p t g r o u p >
< optgroup label = "🆓 Groq (Free)" >
< option value = "groq-llama-3.3" > 🆓 Llama 3.3 70 B < / o p t i o n >
< option value = "groq-llama-3.1" > 🆓 Llama 3.1 8 B < / o p t i o n >
< / o p t g r o u p >
< optgroup label = "🤖 Auto" >
< option value = "auto" > 🤖 Auto ( Smart Route ) < / o p t i o n >
< / o p t g r o u p >
< / s e l e c t >
< label for = "multiplierSelect" > Tokens < / l a b e l >
< select class = "multiplier-select" id = "multiplierSelect" title = "Token multiplier — scales max response length" >
< option value = "1" > 1 x < / o p t i o n >
< option value = "30" selected > 30 x < / o p t i o n >
< option value = "60" > 60 x < / o p t i o n >
< option value = "120" > 120 x < / o p t i o n >
< option value = "300" > 300 x < / o p t i o n >
< option value = "600" > 600 x < / o p t i o n >
< / s e l e c t >
< / d i v >
< div class = "ide-quick-bar" >
< span class = "ide-quick-label" > IDE < / s p a n >
< button type = "button" class = "ide-q-btn" id = "acIdeTerminal" title = "New terminal" > Terminal < / b u t t o n >
< button type = "button" class = "ide-q-btn" id = "acIdeSave" title = "Save file" > Save < / b u t t o n >
< button type = "button" class = "ide-q-btn" id = "acIdeSaveAll" title = "Save all" > Save all < / b u t t o n >
< button type = "button" class = "ide-q-btn" id = "acIdePalette" title = "Command palette" > Commands < / b u t t o n >
< button type = "button" class = "ide-q-btn" id = "acIdeSplit" title = "Split editor" > Split < / b u t t o n >
< button type = "button" class = "ide-q-btn" id = "acIdeNew" title = "New untitled file" > New file < / b u t t o n >
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
2026-04-07 12:11:42 -04:00
< button type = "button" class = "ide-q-btn" id = "acIdeGit" title = "Open source control" > Git < / b u t t o n >
< button type = "button" class = "ide-q-btn" id = "acIdeProblems" title = "Show problems panel" > Problems < / b u t t o n >
< button type = "button" class = "ide-q-btn" id = "acIdeSearch" title = "Search across files" > Search < / b u t t o n >
< button type = "button" class = "ide-q-btn" id = "acIdeFormat" title = "Format document" > Format < / b u t t o n >
2026-04-07 11:40:25 -04:00
< / d i v >
< div class = "chat-area" id = "chatArea" >
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
2026-04-07 12:11:42 -04:00
< div class = "message alfred" id = "greetingMsg" >
2026-04-07 11:40:25 -04:00
< div class = "sender" > Alfred < / d i v >
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
2026-04-07 12:11:42 -04:00
< div > Initializing workspace intelligence ... < / d i v >
2026-04-07 11:40:25 -04:00
< / d i v >
< / d i v >
< div class = "attach-panel empty" id = "attachPanel" > < / d i v >
< div class = "image-preview-strip empty" id = "imagePreviewStrip" > < / d i v >
< div class = "controls-wrap" >
< input type = "text" class = "text-input" id = "textInput" placeholder = "Ask Alfred… (Ctrl+V to paste image)" autocomplete = "off" >
< div class = "action-row" >
< button type = "button" class = "mic-btn" id = "micBtn" title = "Voice input (Whisper STT)" >
< svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke - width = "2" stroke - linecap = "round" stroke - linejoin = "round" >
< path d = "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" / >
< path d = "M19 10v2a7 7 0 0 1-14 0v-2" / >
< line x1 = "12" y1 = "19" x2 = "12" y2 = "23" / >
< line x1 = "8" y1 = "23" x2 = "16" y2 = "23" / >
< / s v g >
< / b u t t o n >
< button type = "button" class = "attach-btn" id = "attachBtn" title = "Attach image, PDF, code, text, or ZIP" > 📎 < / b u t t o n >
< input type = "file" id = "fileInput" accept = "image/*,.pdf,.zip,text/*,.md,.txt,.json,.js,.jsx,.ts,.tsx,.php,.py,.html,.css,.scss,.xml,.yml,.yaml,.sh,.log,.csv,.sql" multiple style = "display:none" >
< button type = "button" class = "send-btn" id = "ac-send" title = "Send to Alfred API — not a link" onclick = "if(typeof dispatchSend==='function'){dispatchSend(event)}else{document.getElementById('status').textContent='Script not loaded — refresh page'}" > SEND < / b u t t o n >
< / d i v >
< / d i v >
< div class = "meter" id = "payloadMeter" > Payload : 0 KB
< div class = "bar" > < div class = "fill" id = "payloadFill" > < / d i v > < / d i v >
< / d i v >
< div class = "bottom-bar" >
< div class = "status" id = "status" > Ready < / d i v >
< button class = "ctrl-btn voice-btn" id = "voiceToggle" > Voice ON < / b u t t o n >
< button class = "ctrl-btn hf-btn" id = "handsFreeBtn" title = "Hands-free: auto-listen after Alfred speaks" > Hands - Free < / b u t t o n >
< / d i v >
< script >
let vscode ;
try { vscode = acquireVsCodeApi ( ) ; } catch ( e ) { vscode = { postMessage : function ( ) { } , setState : function ( ) { } , getState : function ( ) { } } ; }
// Injected session data from extension host (server-side read of session.json)
window . _ _alfredToken = '${safeToken}' ;
window . _ _alfredIdentity = $ { safeIdentity } ;
window . _ _alfredCsrf = null ;
window . _ _alfredSessionCookie = null ;
const chatArea = document . getElementById ( 'chatArea' ) ;
const micBtn = document . getElementById ( 'micBtn' ) ;
const textInput = document . getElementById ( 'textInput' ) ;
const sendBtn = document . getElementById ( 'ac-send' ) ;
const statusEl = document . getElementById ( 'status' ) ;
const agentSelect = document . getElementById ( 'agentSelect' ) ;
const modelSelect = document . getElementById ( 'modelSelect' ) ;
const multiplierSelect = document . getElementById ( 'multiplierSelect' ) ;
const handsFreeBtn = document . getElementById ( 'handsFreeBtn' ) ;
const attachBtn = document . getElementById ( 'attachBtn' ) ;
const fileInput = document . getElementById ( 'fileInput' ) ;
const imagePreviewStrip = document . getElementById ( 'imagePreviewStrip' ) ;
const attachPanel = document . getElementById ( 'attachPanel' ) ;
const payloadMeter = document . getElementById ( 'payloadMeter' ) ;
const payloadFill = document . getElementById ( 'payloadFill' ) ;
const identityBadge = document . getElementById ( 'identityBadge' ) ;
let isRecording = false , isTranscribing = false , isSpeaking = false , messageId = 0 ;
let voiceEnabled = true , currentAudio = null ;
let handsFreeMode = false ;
let mediaRecorder = null , audioChunks = [ ] , mediaStream = null ;
let silenceTimer = null ;
let sttIdCounter = 0 ;
let pendingSttResolve = null ;
let pendingImages = [ ] ; // { id, name, base64, mime, dataUrl, size }
let pendingPdfFiles = [ ] ; // { id, name, data, size }
let pendingTextFiles = [ ] ; // { id, name, text, size }
let pendingZipFiles = [ ] ; // { id, name, data, size }
let pendingAttachments = [ ] ; // ordered queue of ids
let attachmentIdCounter = 0 ;
let pendingAttachmentReads = 0 ;
let queuedSend = false ;
let lastRequest = null ;
function createAttachmentReadGuard ( label , timeoutMs ) {
pendingAttachmentReads += 1 ;
let done = false ;
const timer = setTimeout ( ( ) => {
if ( done ) return ;
done = true ;
if ( pendingAttachmentReads > 0 ) pendingAttachmentReads -= 1 ;
setStatus ( label + ' processing timed out' ) ;
logTelemetry ( 'attachment-timeout: ' + label ) ;
if ( pendingAttachmentReads === 0 && queuedSend ) {
queuedSend = false ;
dispatchSend ( ) ;
}
} , timeoutMs || 15000 ) ;
return ( ) => {
if ( done ) return ;
done = true ;
clearTimeout ( timer ) ;
finalizeAttachmentRead ( ) ;
} ;
}
function logTelemetry ( line ) {
const telemetryDrawer = null ;
if ( ! telemetryDrawer ) return ;
const ts = new Date ( ) . toLocaleTimeString ( ) ;
const div = document . createElement ( 'div' ) ;
div . className = 'telemetry-line' ;
div . textContent = '[' + ts + '] ' + line ;
telemetryDrawer . appendChild ( div ) ;
while ( telemetryDrawer . childElementCount > 120 ) {
telemetryDrawer . removeChild ( telemetryDrawer . firstChild ) ;
}
telemetryDrawer . scrollTop = telemetryDrawer . scrollHeight ;
}
function bytesToKB ( n ) {
return Math . round ( ( n || 0 ) / 1024 ) ;
}
function updatePayloadMeter ( ) {
if ( ! payloadMeter || ! payloadFill ) return ;
let total = 0 ;
for ( const i of pendingImages ) total += i . size || 0 ;
for ( const p of pendingPdfFiles ) total += p . size || 0 ;
for ( const t of pendingTextFiles ) total += t . size || 0 ;
for ( const z of pendingZipFiles ) total += z . size || 0 ;
const kb = bytesToKB ( total ) ;
if ( payloadMeter . firstChild ) payloadMeter . firstChild . textContent = 'Payload: ' + kb + ' KB' ;
const pct = Math . min ( 100 , Math . round ( ( total / ( 10 * 1024 * 1024 ) ) * 100 ) ) ;
payloadFill . style . width = pct + '%' ;
}
function applyIdentity ( profile ) {
const p = profile || { } ;
const name = p . name || p . user || 'User' ;
if ( ! identityBadge ) return ;
identityBadge . textContent = 'Identity: ' + name ;
const verified = p . verified === true || ! ! p . client _id ;
const guestish = ! verified || String ( name ) . toLowerCase ( ) === 'guest' ;
identityBadge . classList . toggle ( 'guest' , guestish ) ;
identityBadge . classList . toggle ( 'verified' , ! guestish ) ;
identityBadge . style . display = ! guestish ? 'inline-block' : 'none' ;
logTelemetry ( 'identity: ' + name + ( p . client _id ? ' (id=' + p . client _id + ')' : '' ) + ( verified ? ' [verified]' : ' [guest]' ) ) ;
// Usage/balance display
const usageInfo = document . getElementById ( 'usageInfo' ) ;
const planLabel = document . getElementById ( 'planLabel' ) ;
const usageText = document . getElementById ( 'usageText' ) ;
const usageFill = document . getElementById ( 'usageFill' ) ;
if ( ! usageInfo || ! planLabel || ! usageText || ! usageFill ) return ;
if ( guestish || ! p . plan ) { usageInfo . style . display = 'none' ; return ; }
usageInfo . style . display = 'block' ;
const plan = ( p . plan || 'free' ) . toLowerCase ( ) ;
planLabel . textContent = plan === 'commander' ? 'Commander' : ( p . plan || 'Free' ) ;
planLabel . className = 'plan-label plan-' + plan ;
if ( p . unlimited || plan === 'commander' ) {
usageText . textContent = 'Unlimited' ;
usageFill . className = 'usage-fill unlimited' ;
usageFill . style . width = '100%' ;
} else {
const used = p . tokens _used || 0 ;
const included = p . tokens _included || 50000 ;
const remaining = Math . max ( 0 , included - used ) ;
const pct = included > 0 ? Math . min ( 100 , Math . round ( ( used / included ) * 100 ) ) : 0 ;
const fmtK = ( n ) => n >= 1000000 ? ( n / 1000000 ) . toFixed ( 1 ) + 'M' : n >= 1000 ? ( n / 1000 ) . toFixed ( 0 ) + 'K' : String ( n ) ;
usageText . textContent = fmtK ( remaining ) + ' tokens left (' + fmtK ( used ) + ' / ' + fmtK ( included ) + ')' ;
usageFill . style . width = pct + '%' ;
usageFill . className = 'usage-fill' + ( pct >= 90 ? ' danger' : pct >= 70 ? ' warn' : '' ) ;
}
}
function findAttachmentById ( id ) {
const img = pendingImages . find ( x => x . id === id ) ;
if ( img ) return { kind : 'image' , item : img } ;
const pdf = pendingPdfFiles . find ( x => x . id === id ) ;
if ( pdf ) return { kind : 'pdf' , item : pdf } ;
const txt = pendingTextFiles . find ( x => x . id === id ) ;
if ( txt ) return { kind : 'text' , item : txt } ;
const zip = pendingZipFiles . find ( x => x . id === id ) ;
if ( zip ) return { kind : 'zip' , item : zip } ;
return null ;
}
function setAttachmentStatusById ( id , status , detail ) {
const ref = findAttachmentById ( id ) ;
if ( ! ref || ! ref . item ) return ;
ref . item . status = status || 'ready' ;
ref . item . detail = detail || '' ;
}
function summarizeAttachmentReport ( report ) {
if ( ! Array . isArray ( report ) || report . length === 0 ) return '' ;
return report . map ( ( item ) => {
const name = item && item . name ? item . name : ( item && item . type ? item . type : 'attachment' ) ;
const status = item && item . status ? item . status : 'processed' ;
const detail = item && item . detail ? item . detail : '' ;
return name + ': ' + status + ( detail ? ' (' + detail + ')' : '' ) ;
} ) . join ( ' | ' ) ;
}
function applyAttachmentReport ( report ) {
if ( ! Array . isArray ( report ) || report . length === 0 ) return ;
for ( const item of report ) {
const name = String ( item && item . name ? item . name : '' ) ;
const ref = pendingAttachments . map ( ( id ) => findAttachmentById ( id ) ) . find ( ( entry ) => entry && entry . item && entry . item . name === name ) ;
if ( ! ref ) continue ;
ref . item . status = item . status || 'ok' ;
ref . item . detail = item . detail || '' ;
}
renderAttachmentPanel ( ) ;
const summary = summarizeAttachmentReport ( report ) ;
if ( summary ) {
addMessage ( 'system' , 'Attachment report: ' + summary ) ;
}
}
function removeAttachmentById ( id ) {
pendingImages = pendingImages . filter ( x => x . id !== id ) ;
pendingPdfFiles = pendingPdfFiles . filter ( x => x . id !== id ) ;
pendingTextFiles = pendingTextFiles . filter ( x => x . id !== id ) ;
pendingZipFiles = pendingZipFiles . filter ( x => x . id !== id ) ;
pendingAttachments = pendingAttachments . filter ( x => x !== id ) ;
renderAttachmentPanel ( ) ;
updatePayloadMeter ( ) ;
}
function moveAttachment ( id , dir ) {
const idx = pendingAttachments . indexOf ( id ) ;
if ( idx < 0 ) return ;
const ni = idx + dir ;
if ( ni < 0 || ni >= pendingAttachments . length ) return ;
const tmp = pendingAttachments [ idx ] ;
pendingAttachments [ idx ] = pendingAttachments [ ni ] ;
pendingAttachments [ ni ] = tmp ;
renderAttachmentPanel ( ) ;
}
function renderAttachmentPanel ( ) {
if ( ! attachPanel || ! imagePreviewStrip ) return ;
attachPanel . innerHTML = '' ;
if ( pendingAttachments . length === 0 ) {
attachPanel . classList . add ( 'empty' ) ;
imagePreviewStrip . classList . add ( 'empty' ) ;
imagePreviewStrip . innerHTML = '' ;
return ;
}
attachPanel . classList . remove ( 'empty' ) ;
imagePreviewStrip . innerHTML = '' ;
const previewImgs = pendingAttachments
. map ( id => findAttachmentById ( id ) )
. filter ( x => x && x . kind === 'image' )
. slice ( 0 , 6 ) ;
if ( previewImgs . length > 0 ) {
imagePreviewStrip . classList . remove ( 'empty' ) ;
for ( const ref of previewImgs ) {
const thumb = document . createElement ( 'div' ) ;
thumb . className = 'img-thumb' ;
const img = document . createElement ( 'img' ) ;
img . src = ref . item . dataUrl ;
thumb . appendChild ( img ) ;
imagePreviewStrip . appendChild ( thumb ) ;
}
} else {
imagePreviewStrip . classList . add ( 'empty' ) ;
}
for ( let i = 0 ; i < pendingAttachments . length ; i ++ ) {
const id = pendingAttachments [ i ] ;
const ref = findAttachmentById ( id ) ;
if ( ! ref ) continue ;
const row = document . createElement ( 'div' ) ;
row . className = 'attach-row' ;
const icon = document . createElement ( 'span' ) ;
icon . textContent = ref . kind === 'image' ? '🖼️' : ( ref . kind === 'pdf' ? '📄' : ( ref . kind === 'zip' ? '📦' : '🧾' ) ) ;
const name = document . createElement ( 'div' ) ;
name . className = 'attach-name' ;
name . textContent = ref . item . name || ( ref . kind + ' attachment' ) ;
const meta = document . createElement ( 'span' ) ;
meta . className = 'attach-meta' ;
meta . textContent = bytesToKB ( ref . item . size || 0 ) + ' KB' ;
const state = document . createElement ( 'span' ) ;
const statusValue = String ( ref . item . status || 'ready' ) ;
state . className = 'attach-state ' + statusValue ;
state . textContent = statusValue ;
const detail = document . createElement ( 'span' ) ;
detail . className = 'attach-detail' ;
detail . textContent = ref . item . detail || '' ;
const up = document . createElement ( 'button' ) ; up . className = 'small-btn' ; up . textContent = '↑' ; up . onclick = ( ) => moveAttachment ( id , - 1 ) ;
const dn = document . createElement ( 'button' ) ; dn . className = 'small-btn' ; dn . textContent = '↓' ; dn . onclick = ( ) => moveAttachment ( id , 1 ) ;
const rm = document . createElement ( 'button' ) ; rm . className = 'small-btn' ; rm . textContent = '✕' ; rm . onclick = ( ) => removeAttachmentById ( id ) ;
row . appendChild ( icon ) ;
row . appendChild ( name ) ;
row . appendChild ( meta ) ;
row . appendChild ( state ) ;
row . appendChild ( detail ) ;
row . appendChild ( up ) ;
row . appendChild ( dn ) ;
row . appendChild ( rm ) ;
attachPanel . appendChild ( row ) ;
}
}
// ── Image attachment helpers ──────────────────────────────────────────────
function addPendingImage ( base64 , mime , dataUrl , fileName ) {
if ( pendingImages . length >= 5 ) { setStatus ( 'Max 5 images per message' ) ; return ; }
const id = ++ attachmentIdCounter ;
const size = Math . floor ( ( base64 . length * 3 ) / 4 ) ;
pendingImages . push ( { id , name : fileName || ( 'image-' + id ) , base64 , mime , dataUrl , size , status : 'ready' , detail : 'Ready to send' } ) ;
pendingAttachments . push ( id ) ;
renderAttachmentPanel ( ) ;
updatePayloadMeter ( ) ;
}
function clearPendingImages ( ) {
pendingImages = [ ] ;
pendingPdfFiles = [ ] ;
pendingTextFiles = [ ] ;
pendingZipFiles = [ ] ;
pendingAttachments = [ ] ;
if ( imagePreviewStrip ) {
imagePreviewStrip . innerHTML = '' ;
imagePreviewStrip . classList . add ( 'empty' ) ;
}
renderAttachmentPanel ( ) ;
updatePayloadMeter ( ) ;
}
function finalizeAttachmentRead ( ) {
if ( pendingAttachmentReads > 0 ) pendingAttachmentReads -= 1 ;
if ( pendingAttachmentReads === 0 && queuedSend ) {
queuedSend = false ;
dispatchSend ( ) ;
}
}
function isZipFile ( file ) {
const name = String ( file ? . name || '' ) . toLowerCase ( ) ;
const type = String ( file ? . type || '' ) . toLowerCase ( ) ;
return name . endsWith ( '.zip' ) || type === 'application/zip' || type === 'application/x-zip-compressed' || type === 'multipart/x-zip' ;
}
function isPdfFile ( file ) {
const name = String ( file ? . name || '' ) . toLowerCase ( ) ;
const type = String ( file ? . type || '' ) . toLowerCase ( ) ;
return type === 'application/pdf' || name . endsWith ( '.pdf' ) ;
}
function isTextLikeFile ( file ) {
const name = String ( file ? . name || '' ) . toLowerCase ( ) ;
const type = String ( file ? . type || '' ) . toLowerCase ( ) ;
if ( type . startsWith ( 'text/' ) ) return true ;
if ( type . includes ( 'json' ) || type . includes ( 'javascript' ) || type . includes ( 'typescript' ) || type . includes ( 'xml' ) || type . includes ( 'yaml' ) || type . includes ( 'python' ) || type . includes ( 'shell' ) ) return true ;
return /\.(txt|md|markdown|json|js|jsx|ts|tsx|php|py|rb|go|java|c|cc|cpp|h|hpp|cs|rs|swift|kt|m|mm|scala|sql|html|css|scss|sass|less|xml|yml|yaml|sh|bash|zsh|env|ini|conf|cfg|log|csv)$/i . test ( name ) ;
}
function processAttachmentFile ( file ) {
if ( ! file ) return ;
const fileType = String ( file . type || '' ) . toLowerCase ( ) ;
if ( isPdfFile ( file ) ) {
if ( file . size > 8 * 1024 * 1024 ) { setStatus ( 'PDF too large (max 8MB)' ) ; return ; }
const finishRead = createAttachmentReadGuard ( 'PDF' , 15000 ) ;
setStatus ( 'Processing PDF...' , true ) ;
const r = new FileReader ( ) ;
r . onload = e => {
const dataUrl = String ( e . target . result || '' ) ;
if ( ! dataUrl . includes ( ',' ) ) { setStatus ( 'Failed to process PDF' ) ; finishRead ( ) ; return ; }
const base64 = dataUrl . split ( ',' ) [ 1 ] ;
const id = ++ attachmentIdCounter ;
pendingPdfFiles . push ( { id , name : file . name || ( 'file-' + id + '.pdf' ) , data : base64 , size : file . size || 0 , status : 'ready' , detail : 'Awaiting extraction' } ) ;
pendingAttachments . push ( id ) ;
renderAttachmentPanel ( ) ;
updatePayloadMeter ( ) ;
setStatus ( 'PDF attached' ) ;
finishRead ( ) ;
} ;
r . onerror = ( ) => { setStatus ( 'Failed to process PDF' ) ; finishRead ( ) ; } ;
r . readAsDataURL ( file ) ;
return ;
}
if ( isZipFile ( file ) ) {
if ( file . size > 10 * 1024 * 1024 ) { setStatus ( 'ZIP too large (max 10MB)' ) ; return ; }
const finishRead = createAttachmentReadGuard ( 'ZIP' , 15000 ) ;
setStatus ( 'Processing ZIP...' , true ) ;
const zipReader = new FileReader ( ) ;
zipReader . onload = e => {
const dataUrl = String ( e . target . result || '' ) ;
if ( ! dataUrl . includes ( ',' ) ) { setStatus ( 'Failed to process ZIP' ) ; finishRead ( ) ; return ; }
const base64 = dataUrl . split ( ',' ) [ 1 ] ;
const id = ++ attachmentIdCounter ;
pendingZipFiles . push ( { id , name : file . name || ( 'archive-' + id + '.zip' ) , data : base64 , size : file . size || 0 , status : 'ready' , detail : 'Awaiting extraction' } ) ;
pendingAttachments . push ( id ) ;
renderAttachmentPanel ( ) ;
updatePayloadMeter ( ) ;
setStatus ( 'ZIP attached' ) ;
finishRead ( ) ;
} ;
zipReader . onerror = ( ) => { setStatus ( 'Failed to process ZIP' ) ; finishRead ( ) ; } ;
zipReader . readAsDataURL ( file ) ;
return ;
}
if ( isTextLikeFile ( file ) ) {
if ( file . size > 300 * 1024 ) {
setStatus ( 'Text file too large (max 300KB)' ) ;
return ;
}
const finishRead = createAttachmentReadGuard ( 'Text attachment' , 12000 ) ;
setStatus ( 'Reading text attachment...' , true ) ;
const textReader = new FileReader ( ) ;
textReader . onload = e => {
const content = String ( e . target . result || '' ) . slice ( 0 , 12000 ) ;
const safeName = String ( file . name || 'attachment.txt' ) . split ( String . fromCharCode ( 96 ) ) . join ( '' ) ;
const id = ++ attachmentIdCounter ;
pendingTextFiles . push ( { id , name : safeName , text : content , size : file . size || content . length , status : 'ready' , detail : 'Ready to send' } ) ;
pendingAttachments . push ( id ) ;
renderAttachmentPanel ( ) ;
updatePayloadMeter ( ) ;
setStatus ( 'Text file attached' ) ;
finishRead ( ) ;
} ;
textReader . onerror = ( ) => { setStatus ( 'Failed to read text file' ) ; finishRead ( ) ; } ;
textReader . readAsText ( file ) ;
return ;
}
if ( ! fileType . startsWith ( 'image/' ) ) {
setStatus ( 'Unsupported file: ' + ( file . name || 'attachment' ) ) ;
return ;
}
if ( file . size > 5 * 1024 * 1024 ) { setStatus ( 'Image too large (max 5MB)' ) ; return ; }
const finishRead = createAttachmentReadGuard ( 'Image' , 12000 ) ;
setStatus ( 'Processing image...' , true ) ;
const reader = new FileReader ( ) ;
reader . onload = e => {
const dataUrl = String ( e . target . result || '' ) ;
if ( ! dataUrl . includes ( ',' ) ) { setStatus ( 'Failed to process image' ) ; finishRead ( ) ; return ; }
const base64 = dataUrl . split ( ',' ) [ 1 ] ;
const mime = file . type ;
addPendingImage ( base64 , mime , dataUrl , file . name || 'image' ) ;
setStatus ( 'Image attached' ) ;
finishRead ( ) ;
} ;
reader . onerror = ( ) => { setStatus ( 'Failed to process image' ) ; finishRead ( ) ; } ;
reader . readAsDataURL ( file ) ;
}
// Paste handler (Ctrl+V image)
document . addEventListener ( 'paste' , e => {
let handled = false ;
const items = e . clipboardData ? . items ;
if ( items && items . length ) {
for ( const item of items ) {
if ( item && item . kind === 'file' ) {
const f = item . getAsFile ( ) ;
if ( f ) {
e . preventDefault ( ) ;
processAttachmentFile ( f ) ;
handled = true ;
}
}
}
}
if ( ! handled ) {
const files = e . clipboardData ? . files ;
if ( files && files . length ) {
for ( const f of files ) {
if ( f ) {
e . preventDefault ( ) ;
processAttachmentFile ( f ) ;
handled = true ;
}
}
}
}
if ( handled ) setStatus ( 'Attachment added' , false ) ;
} ) ;
// Drag-and-drop onto chat area
if ( chatArea ) {
chatArea . addEventListener ( 'dragover' , e => { e . preventDefault ( ) ; chatArea . style . outline = '2px dashed #e2b340' ; } ) ;
chatArea . addEventListener ( 'dragleave' , ( ) => { chatArea . style . outline = '' ; } ) ;
chatArea . addEventListener ( 'drop' , e => {
e . preventDefault ( ) ;
chatArea . style . outline = '' ;
Array . from ( e . dataTransfer . files || [ ] ) . forEach ( processAttachmentFile ) ;
} ) ;
}
// File picker button
if ( attachBtn && fileInput ) {
attachBtn . addEventListener ( 'click' , ( ) => fileInput . click ( ) ) ;
fileInput . addEventListener ( 'change' , ( ) => {
Array . from ( fileInput . files || [ ] ) . forEach ( processAttachmentFile ) ;
fileInput . value = '' ;
} ) ;
}
if ( agentSelect ) {
agentSelect . addEventListener ( 'change' , ( ) => {
vscode . postMessage ( { type : 'set-agent' , agent : agentSelect . value } ) ;
addMessage ( 'system' , 'Switched to ' + agentSelect . options [ agentSelect . selectedIndex ] . text ) ;
} ) ;
}
if ( modelSelect ) {
modelSelect . addEventListener ( 'change' , ( ) => {
addMessage ( 'system' , 'Model: ' + modelSelect . options [ modelSelect . selectedIndex ] . text ) ;
} ) ;
}
// ── MediaRecorder-based recording ──────────────────────────────────────────
async function getMicStream ( ) {
if ( mediaStream ) return mediaStream ;
try {
mediaStream = await navigator . mediaDevices . getUserMedia ( {
audio : { channelCount : 1 , sampleRate : 16000 , echoCancellation : true , noiseSuppression : true }
} ) ;
return mediaStream ;
} catch ( e ) {
setStatus ( 'Mic access denied: ' + e . message ) ;
return null ;
}
}
async function startRecording ( ) {
if ( isRecording || isTranscribing ) return ;
if ( isSpeaking ) { stopAudio ( ) ; }
const stream = await getMicStream ( ) ;
if ( ! stream ) {
setStatus ( 'No microphone access' ) ;
if ( handsFreeMode ) toggleHandsFree ( ) ;
return ;
}
audioChunks = [ ] ;
const mimeType = MediaRecorder . isTypeSupported ( 'audio/webm;codecs=opus' ) ? 'audio/webm;codecs=opus'
: MediaRecorder . isTypeSupported ( 'audio/webm' ) ? 'audio/webm'
: MediaRecorder . isTypeSupported ( 'audio/ogg;codecs=opus' ) ? 'audio/ogg;codecs=opus'
: 'audio/mp4' ;
try {
mediaRecorder = new MediaRecorder ( stream , { mimeType } ) ;
} catch ( e ) {
mediaRecorder = new MediaRecorder ( stream ) ;
}
mediaRecorder . ondataavailable = ( e ) => { if ( e . data . size > 0 ) audioChunks . push ( e . data ) ; } ;
mediaRecorder . onstop = ( ) => { finishRecording ( ) ; } ;
mediaRecorder . start ( 250 ) ;
isRecording = true ;
micBtn . classList . add ( 'recording' ) ;
micBtn . classList . remove ( 'transcribing' ) ;
setStatus ( 'Recording...' , true ) ;
silenceTimer = setTimeout ( ( ) => {
if ( isRecording ) stopRecording ( ) ;
} , 15000 ) ;
}
function stopRecording ( ) {
if ( ! isRecording || ! mediaRecorder ) return ;
clearTimeout ( silenceTimer ) ;
isRecording = false ;
try { mediaRecorder . stop ( ) ; } catch ( e ) { }
}
async function finishRecording ( ) {
micBtn . classList . remove ( 'recording' ) ;
if ( audioChunks . length === 0 ) {
setStatus ( 'No audio captured' ) ;
if ( handsFreeMode ) scheduleHandsFreeRecord ( ) ;
return ;
}
isTranscribing = true ;
micBtn . classList . add ( 'transcribing' ) ;
setStatus ( 'Transcribing...' , true ) ;
const blob = new Blob ( audioChunks , { type : mediaRecorder . mimeType || 'audio/webm' } ) ;
audioChunks = [ ] ;
if ( blob . size < 1000 ) {
isTranscribing = false ;
micBtn . classList . remove ( 'transcribing' ) ;
setStatus ( 'Too short' ) ;
if ( handsFreeMode ) scheduleHandsFreeRecord ( ) ;
return ;
}
const reader = new FileReader ( ) ;
reader . onloadend = ( ) => {
const base64 = reader . result . split ( ',' ) [ 1 ] ;
const id = ++ sttIdCounter ;
vscode . postMessage ( { type : 'stt-request' , audio : base64 , mime : blob . type , id } ) ;
} ;
reader . readAsDataURL ( blob ) ;
}
function toggleRecording ( ) {
if ( isRecording ) stopRecording ( ) ;
else if ( isTranscribing ) { /* wait */ }
else startRecording ( ) ;
}
// ── Audio playback ─────────────────────────────────────────────────────────
function stopAudio ( ) {
if ( currentAudio ) { currentAudio . pause ( ) ; currentAudio . currentTime = 0 ; currentAudio = null ; }
isSpeaking = false ;
setStatus ( 'Ready' ) ;
}
function playAudio ( dataUrl ) {
if ( ! voiceEnabled || ! dataUrl ) return ;
stopAudio ( ) ;
isSpeaking = true ;
setStatus ( 'Speaking...' , true ) ;
currentAudio = new Audio ( dataUrl ) ;
currentAudio . onended = ( ) => {
isSpeaking = false ;
currentAudio = null ;
setStatus ( 'Ready' ) ;
if ( handsFreeMode ) scheduleHandsFreeRecord ( ) ;
} ;
currentAudio . onerror = ( ) => {
isSpeaking = false ;
currentAudio = null ;
setStatus ( 'Ready' ) ;
if ( handsFreeMode ) scheduleHandsFreeRecord ( ) ;
} ;
currentAudio . play ( ) . catch ( ( ) => {
isSpeaking = false ;
setStatus ( 'Ready' ) ;
if ( handsFreeMode ) scheduleHandsFreeRecord ( ) ;
} ) ;
}
// ── Hands-free mode ────────────────────────────────────────────────────────
function toggleHandsFree ( ) {
handsFreeMode = ! handsFreeMode ;
handsFreeBtn . classList . toggle ( 'active' , handsFreeMode ) ;
handsFreeBtn . textContent = handsFreeMode ? 'HF Active' : 'Hands-Free' ;
if ( handsFreeMode ) {
addMessage ( 'system' , 'Hands-free mode ON — auto-listen after Alfred speaks' ) ;
if ( ! isRecording && ! isTranscribing && ! isSpeaking ) {
scheduleHandsFreeRecord ( ) ;
}
} else {
addMessage ( 'system' , 'Hands-free mode OFF' ) ;
if ( isRecording ) stopRecording ( ) ;
}
}
function scheduleHandsFreeRecord ( ) {
if ( ! handsFreeMode || isRecording || isTranscribing || isSpeaking ) return ;
setTimeout ( ( ) => {
if ( handsFreeMode && ! isRecording && ! isTranscribing && ! isSpeaking ) {
startRecording ( ) ;
}
} , 800 ) ;
}
// ── Message formatting ─────────────────────────────────────────────────────
function formatResponse ( text ) {
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
2026-04-07 12:11:42 -04:00
let blockId = 0 ;
2026-04-07 11:40:25 -04:00
return text
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
2026-04-07 12:11:42 -04:00
. replace ( /\`\`\`(\\w*)\\n([\\s\\S]*?)\`\`\`/g , function ( match , lang , code ) {
blockId ++ ;
const escapedCode = code . replace ( /&/g , '&' ) . replace ( /</g , '<' ) . replace ( />/g , '>' ) ;
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>' ;
} )
2026-04-07 11:40:25 -04:00
. replace ( /\`([^\`]+)\`/g , '<code>$1</code>' )
. replace ( /\\*\\*([^*]+)\\*\\*/g , '<strong>$1</strong>' )
. replace ( /\\n/g , '<br>' ) ;
}
function formatResponseSafe ( raw ) {
try {
return formatResponse ( raw == null ? '' : String ( raw ) ) ;
} catch ( e ) {
return String ( raw == null ? '' : raw )
. replace ( /&/g , '&' ) . replace ( /</g , '<' ) . replace ( />/g , '>' )
. replace ( /\\n/g , '<br>' ) ;
}
}
function addMessage ( sender , text , isThinking , requestId ) {
const div = document . createElement ( 'div' ) ;
div . className = 'message ' + ( sender === 'commander' ? 'commander' : sender === 'system' ? 'system' : 'alfred' ) ;
if ( requestId != null && requestId !== '' ) {
div . setAttribute ( 'data-alfred-req-id' , String ( requestId ) ) ;
}
if ( sender !== 'system' ) {
const s = document . createElement ( 'div' ) ; s . className = 'sender' ;
s . textContent = sender === 'commander' ? 'Commander' : sender . charAt ( 0 ) . toUpperCase ( ) + sender . slice ( 1 ) ;
div . appendChild ( s ) ;
}
const b = document . createElement ( 'div' ) ;
b . className = 'msg-content' ;
if ( isThinking ) b . innerHTML = '<span class="thinking">Thinking</span>' ;
else if ( sender === 'commander' || sender === 'system' ) b . textContent = text ;
else b . innerHTML = formatResponseSafe ( text ) ;
div . appendChild ( b ) ;
chatArea . appendChild ( div ) ;
chatArea . scrollTop = chatArea . scrollHeight ;
return b ;
}
function setStatus ( text , active ) {
if ( ! statusEl ) return ;
statusEl . textContent = text ;
statusEl . className = 'status' + ( active ? ' active' : '' ) ;
}
function processInput ( text ) {
if ( ! text ) return ;
const attachmentCount = pendingAttachments . length ;
const attachmentNames = pendingAttachments
. map ( id => findAttachmentById ( id ) )
. filter ( Boolean )
. map ( ref => ref . item && ref . item . name ? ref . item . name : ref . kind )
. slice ( 0 , 6 ) ;
const contextSuffix = attachmentCount > 0
? ( '\\n\\n[Attachments included: ' + attachmentCount + ( attachmentNames . length ? ( ' | ' + attachmentNames . join ( ', ' ) ) : '' ) + ']' )
: '' ;
const modelText = text + contextSuffix ;
if ( pendingAttachments . length > 0 ) {
const imgHtml = pendingImages . map ( p => '<img src="' + p . dataUrl + '" style="max-width:80px;max-height:60px;border-radius:4px;margin:2px;vertical-align:middle;">' ) . join ( '' ) ;
const attachSummary = '<div style="margin-top:4px;font-size:10px;opacity:.8;">Attachments: ' + pendingAttachments . length + '</div>' ;
const msgDiv = addMessage ( 'commander' , text ) ;
msgDiv . parentElement . insertAdjacentHTML ( 'beforeend' , '<div style="margin-top:4px;">' + imgHtml + attachSummary + '</div>' ) ;
} else {
addMessage ( 'commander' , text ) ;
}
if ( text . toLowerCase ( ) . startsWith ( 'run ' ) ) {
vscode . postMessage ( { type : 'run-terminal' , command : text . replace ( /^run\s+/i , '' ) } ) ;
addMessage ( 'system' , 'Sent to terminal — chat not used.' ) ;
return ;
}
if ( text . toLowerCase ( ) . startsWith ( 'insert ' ) || text . toLowerCase ( ) . startsWith ( 'type ' ) ) {
vscode . postMessage ( { type : 'insert-code' , code : text . replace ( /^(insert|type)\s+/i , '' ) } ) ;
addMessage ( 'alfred' , 'Inserted.' ) ; return ;
}
const orderedImages = [ ] ;
const orderedPdfs = [ ] ;
const orderedTexts = [ ] ;
const orderedZips = [ ] ;
for ( const id of pendingAttachments ) {
const ref = findAttachmentById ( id ) ;
if ( ! ref ) continue ;
if ( ref . kind === 'image' ) orderedImages . push ( { data : ref . item . base64 , type : ref . item . mime } ) ;
if ( ref . kind === 'pdf' ) orderedPdfs . push ( { name : ref . item . name , data : ref . item . data } ) ;
if ( ref . kind === 'text' ) orderedTexts . push ( { name : ref . item . name , text : ref . item . text } ) ;
if ( ref . kind === 'zip' ) orderedZips . push ( { name : ref . item . name , data : ref . item . data } ) ;
}
const outgoingModel = modelSelect ? modelSelect . value : 'sonnet' ;
const outgoingMultiplier = multiplierSelect ? parseInt ( multiplierSelect . value , 10 ) : 30 ;
const reqId = ++ messageId ;
addMessage ( agentSelect . value , '' , true , reqId ) ;
setStatus ( agentSelect . options [ agentSelect . selectedIndex ] . text + ' thinking...' , true ) ;
for ( const id of pendingAttachments ) {
setAttachmentStatusById ( id , 'queued' , 'Sending to Alfred' ) ;
}
renderAttachmentPanel ( ) ;
lastRequest = { text : modelText , agent : agentSelect . value , model : outgoingModel , multiplier : outgoingMultiplier , images : orderedImages , pdf _files : orderedPdfs , attachment _texts : orderedTexts , zip _files : orderedZips } ;
logTelemetry ( 'send #' + reqId + ' model=' + outgoingModel + ' multiplier=' + outgoingMultiplier + ' images=' + orderedImages . length + ' pdf=' + orderedPdfs . length + ' text=' + orderedTexts . length + ' zip=' + orderedZips . length ) ;
// PRIMARY: Direct browser fetch to Alfred API (bypasses broken extension host IPC)
alfredDirectChat ( reqId , modelText , agentSelect . value , outgoingModel , orderedImages , orderedPdfs , orderedTexts , orderedZips , outgoingMultiplier ) ;
// SECONDARY: Also notify extension host for TTS, editor context, etc.
try { vscode . postMessage ( { type : 'ai-request' , text : modelText , agent : agentSelect . value , id : reqId , model : outgoingModel , multiplier : outgoingMultiplier , images : orderedImages , pdf _files : orderedPdfs , attachment _texts : orderedTexts , zip _files : orderedZips } ) ; } catch ( _ ) { }
}
// 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 {
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
2026-04-07 12:11:42 -04:00
// Request workspace context from extension host for richer payload
vscode . postMessage ( { type : 'get-context' , id : reqId } ) ;
2026-04-07 11:40:25 -04:00
const payload = {
message : text , agent : agent || 'alfred' , model : model || 'sonnet' ,
token _multiplier : multiplier || 30 ,
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
2026-04-07 12:11:42 -04:00
channel : 'ide-chat' , context : window . _ _lastWorkspaceContext || '' , conv _id : ''
2026-04-07 11:40:25 -04:00
} ;
// Include auth token injected from extension host
if ( window . _ _alfredToken ) payload . ide _session _token = window . _ _alfredToken ;
const ident = window . _ _alfredIdentity ;
if ( ident && ident . ide _client _id ) {
payload . ide _client _id = ident . ide _client _id ;
payload . ide _name = ident . ide _name || '' ;
payload . ide _ts = ident . ide _ts || 0 ;
payload . ide _sig = ident . ide _sig || '' ;
payload . ide _email = ident . ide _email || '' ;
}
if ( images && images . length ) payload . images = images ;
if ( pdfs && pdfs . length ) payload . pdf _files = pdfs ;
if ( texts && texts . length ) payload . attachment _texts = texts ;
if ( zips && zips . length ) payload . zip _files = zips ;
const hdrs = { 'Content-Type' : 'application/json' , 'X-Alfred-Source' : 'alfred-ide' } ;
if ( window . _ _alfredToken ) {
hdrs [ 'Authorization' ] = 'Bearer ' + window . _ _alfredToken ;
hdrs [ 'X-Alfred-IDE-Token' ] = window . _ _alfredToken ;
}
if ( window . _ _alfredCsrf ) hdrs [ 'X-CSRF-Token' ] = window . _ _alfredCsrf ;
logTelemetry ( 'direct-fetch #' + reqId + ' starting...' ) ;
let resp = await fetch ( 'https://gositeme.com/api/alfred-chat.php' , {
method : 'POST' , headers : hdrs , credentials : 'include' ,
body : JSON . stringify ( payload )
} ) ;
let data = await parseApiResponse ( resp ) ;
// Handle CSRF refresh cycle (first request always returns csrf_token)
if ( data . csrf _token ) window . _ _alfredCsrf = data . csrf _token ;
let retries = 0 ;
while ( retries < 3 && ( data . csrf _refresh || data . response === 'Session initialized. Please retry.' || data . error === 'CSRF validation failed' ) ) {
retries ++ ;
hdrs [ 'X-CSRF-Token' ] = window . _ _alfredCsrf || '' ;
resp = await fetch ( 'https://gositeme.com/api/alfred-chat.php' , {
method : 'POST' , headers : hdrs , credentials : 'include' ,
body : JSON . stringify ( payload )
} ) ;
data = await parseApiResponse ( resp ) ;
if ( data . csrf _token ) window . _ _alfredCsrf = data . csrf _token ;
}
const responseText = data . response || data . message || data . error || 'No response from AI' ;
if ( data . identity ) applyIdentity ( data . identity ) ;
if ( data . attachment _report ) applyAttachmentReport ( data . attachment _report ) ;
if ( data . conv _id ) payload . conv _id = data . conv _id ;
const row = chatArea . querySelector ( '.message[data-alfred-req-id="' + reqId + '"]' ) ;
const p = row ? row . querySelector ( '.msg-content' ) : null ;
if ( p ) {
p . innerHTML = formatResponseSafe ( responseText ) ;
const s = row . querySelector ( '.sender' ) ;
if ( s && data . agent ) s . textContent = data . agent . charAt ( 0 ) . toUpperCase ( ) + data . agent . slice ( 1 ) ;
const retry = document . createElement ( 'button' ) ;
retry . className = 'small-btn retry-btn' ; retry . textContent = 'Retry' ;
retry . onclick = ( ) => { if ( ! lastRequest ) return ; const rid = ++ messageId ; addMessage ( lastRequest . agent || 'alfred' , '' , true , rid ) ; alfredDirectChat ( rid , lastRequest . text , lastRequest . agent , lastRequest . model , lastRequest . images , lastRequest . pdf _files , lastRequest . attachment _texts , lastRequest . zip _files , lastRequest . multiplier || 30 ) ; } ;
p . appendChild ( retry ) ;
} else {
addMessage ( data . agent || agent || 'alfred' , responseText ) ;
}
setStatus ( 'Ready' ) ;
logTelemetry ( 'direct-fetch ok #' + reqId ) ;
} catch ( err ) {
const row = chatArea . querySelector ( '.message[data-alfred-req-id="' + reqId + '"]' ) ;
const p = row ? row . querySelector ( '.msg-content' ) : null ;
const msg = normalizeApiError ( err ) ;
if ( p ) p . innerHTML = msg ;
else addMessage ( 'alfred' , msg ) ;
setStatus ( 'Error — try again' ) ;
logTelemetry ( 'direct-fetch error: ' + ( err . message || 'unknown' ) ) ;
}
}
async function parseApiResponse ( resp ) {
const raw = await resp . text ( ) ;
if ( ! raw || ! raw . trim ( ) ) {
throw new Error ( 'empty-response' ) ;
}
try {
return JSON . parse ( raw ) ;
} catch ( _ ) {
return { response : raw . trim ( ) } ;
}
}
function normalizeApiError ( err ) {
const message = String ( ( err && err . message ) || 'unknown' ) ;
if ( message === 'empty-response' ) {
return 'Alfred returned an empty response. Try again.' ;
}
if ( /JSON\.parse|unexpected end of data|Unexpected end of JSON/i . test ( message ) ) {
return 'Alfred returned an invalid response. Try again.' ;
}
return 'API error: ' + message + ' — check network or try again.' ;
}
// ── Message handler from extension host ────────────────────────────────────
window . addEventListener ( 'message' , ( event ) => {
const msg = event . data ;
if ( msg . type === 'ai-response' ) {
// Extension host responded — direct fetch is primary, so only use this for TTS/supplemental
// If the direct fetch did not fill the response, use the extension-host reply.
logTelemetry ( 'ext-host-response #' + ( msg . id || '?' ) + ' received (direct fetch is primary)' ) ;
if ( msg . identity ) applyIdentity ( msg . identity ) ;
if ( msg . attachment _report ) applyAttachmentReport ( msg . attachment _report ) ;
const row = chatArea . querySelector ( '.message[data-alfred-req-id="' + ( msg . id || '' ) + '"]' ) ;
const p = row ? row . querySelector ( '.msg-content' ) : null ;
if ( p && ( /thinking/i . test ( p . textContent || '' ) || /API error:/i . test ( p . textContent || '' ) || /empty response/i . test ( p . textContent || '' ) ) ) {
p . innerHTML = formatResponseSafe ( msg . text || 'No response from AI' ) ;
const s = row . querySelector ( '.sender' ) ;
if ( s && msg . agent ) s . textContent = msg . agent . charAt ( 0 ) . toUpperCase ( ) + msg . agent . slice ( 1 ) ;
setStatus ( 'Ready' ) ;
}
} else if ( msg . type === 'play-audio' ) {
playAudio ( msg . audio ) ;
} else if ( msg . type === 'stt-result' ) {
isTranscribing = false ;
micBtn . classList . remove ( 'transcribing' ) ;
const text = ( msg . text || '' ) . trim ( ) ;
if ( text ) {
setStatus ( 'Heard: ' + text . substring ( 0 , 40 ) + ( text . length > 40 ? '...' : '' ) ) ;
processInput ( text ) ;
} else {
setStatus ( msg . error || 'No speech detected' ) ;
if ( handsFreeMode ) scheduleHandsFreeRecord ( ) ;
}
} else if ( msg . type === 'command-result' ) {
if ( msg . text ) { addMessage ( 'alfred' , msg . text ) ; vscode . postMessage ( { type : 'tts-request' , text : msg . text } ) ; }
} else if ( msg . type === 'toggle-listening' ) { toggleRecording ( ) ; }
else if ( msg . type === 'run-terminal-cmd' ) { vscode . postMessage ( { type : 'run-terminal' , command : msg . command } ) ; }
else if ( msg . type === 'user-profile' ) {
applyIdentity ( msg . profile || { } ) ;
}
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
2026-04-07 12:11:42 -04:00
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 ) ) ;
}
}
2026-04-07 11:40:25 -04:00
} ) ;
// ── Event listeners ────────────────────────────────────────────────────────
if ( micBtn ) micBtn . addEventListener ( 'click' , toggleRecording ) ;
const voiceToggleBtn = document . getElementById ( 'voiceToggle' ) ;
function bindPanelButton ( el , handler ) {
if ( ! el ) return ;
// Capture phase makes these controls resilient when host UI adds bubbling handlers.
el . addEventListener ( 'click' , ( e ) => {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
e . stopImmediatePropagation ( ) ;
try { handler ( ) ; } catch ( err ) { setStatus ( 'Control error: ' + ( err && err . message ? err . message : String ( err ) ) ) ; }
} , true ) ;
}
bindPanelButton ( voiceToggleBtn , ( ) => {
voiceEnabled = ! voiceEnabled ;
voiceToggleBtn . textContent = voiceEnabled ? 'Voice ON' : 'Voice OFF' ;
voiceToggleBtn . classList . toggle ( 'off' , ! voiceEnabled ) ;
if ( ! voiceEnabled ) stopAudio ( ) ;
setStatus ( 'Voice ' + ( voiceEnabled ? 'enabled' : 'disabled' ) ) ;
addMessage ( 'system' , 'Voice ' + ( voiceEnabled ? 'enabled' : 'disabled' ) ) ;
} ) ;
bindPanelButton ( handsFreeBtn , ( ) => toggleHandsFree ( ) ) ;
function dispatchSend ( ev ) {
if ( ev ) {
ev . preventDefault ( ) ;
ev . stopPropagation ( ) ;
ev . stopImmediatePropagation ( ) ;
}
if ( pendingAttachmentReads > 0 ) {
queuedSend = true ;
setStatus ( 'Finishing attachment processing...' , true ) ;
return ;
}
const t = textInput . value . trim ( ) ;
if ( ! t && pendingAttachments . length === 0 ) {
setStatus ( 'Type a message or attach a file' ) ;
return ;
}
textInput . value = '' ;
const msg = t || '(See attached file)' ;
processInput ( msg ) ;
clearPendingImages ( ) ;
updatePayloadMeter ( ) ;
}
function bindSend ( ) {
if ( ! sendBtn ) return ;
// Capture phase — runs before bubble handlers so nothing can hijack Send for navigation.
sendBtn . addEventListener ( 'click' , dispatchSend , true ) ;
}
bindSend ( ) ;
// Backup: if DOM wasn't ready, retry on DOMContentLoaded
if ( document . readyState === 'loading' ) {
document . addEventListener ( 'DOMContentLoaded' , function ( ) { bindSend ( ) ; } ) ;
}
( function bindIdeQuick ( ) {
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
2026-04-07 12:11:42 -04:00
[ [ 'acIdeTerminal' , 'terminal' ] , [ 'acIdeSave' , 'save' ] , [ 'acIdeSaveAll' , 'saveAll' ] , [ 'acIdePalette' , 'palette' ] , [ 'acIdeSplit' , 'split' ] , [ 'acIdeNew' , 'newFile' ] , [ 'acIdeGit' , 'git' ] , [ 'acIdeProblems' , 'problems' ] , [ 'acIdeSearch' , 'search' ] , [ 'acIdeFormat' , 'format' ] ] . forEach ( ( [ id , cmd ] ) => {
2026-04-07 11:40:25 -04:00
const el = document . getElementById ( id ) ;
if ( el ) el . addEventListener ( 'click' , ( e ) => { e . preventDefault ( ) ; e . stopPropagation ( ) ; vscode . postMessage ( { type : 'ide-quick' , cmd } ) ; } ) ;
} ) ;
} ) ( ) ;
if ( textInput ) {
textInput . addEventListener ( 'keydown' , ( e ) => {
if ( e . key === 'Enter' && ! e . shiftKey ) {
e . preventDefault ( ) ;
dispatchSend ( e ) ;
}
} ) ;
}
window . addEventListener ( 'error' , ( ev ) => {
try {
const msg = ( ev && ev . message ) ? ev . message : 'Unknown UI error' ;
setStatus ( 'UI error: ' + msg ) ;
addMessage ( 'system' , '[ERROR] ' + msg ) ;
logTelemetry ( 'ui-error: ' + msg ) ;
} catch ( _ ) { }
} ) ;
window . addEventListener ( 'unhandledrejection' , ( ev ) => {
try {
const msg = ( ev . reason && ev . reason . message ) ? ev . reason . message : String ( ev . reason || 'Unknown promise rejection' ) ;
setStatus ( 'Promise error: ' + msg ) ;
addMessage ( 'system' , '[PROMISE ERROR] ' + msg ) ;
logTelemetry ( 'promise-error: ' + msg ) ;
} catch ( _ ) { }
} ) ;
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
2026-04-07 12:11:42 -04:00
// ── 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>' ;
}
} ) ( ) ;
2026-04-07 11:40:25 -04:00
setStatus ( 'Ready' ) ;
< / s c r i p t >
< / b o d y >
< / h t m l > ` ;
}
function deactivate ( ) { }
function getWorkspaceStatusHTML ( token ) {
return ` <!DOCTYPE html>
< html > < head >
< meta charset = "UTF-8" >
< style >
body { background : # 0e0 e1a ; color : # e0e0e8 ; font - family : 'Segoe UI' , system - ui , sans - serif ; padding : 24 px ; margin : 0 }
h1 { color : # 00 D4FF ; font - size : 22 px ; margin : 0 0 20 px }
. card { background : # 16162 a ; border : 1 px solid # 2 a2a4a ; border - radius : 12 px ; padding : 16 px ; margin - bottom : 16 px }
. card h2 { color : # 00 D4FF ; font - size : 16 px ; margin : 0 0 12 px ; display : flex ; align - items : center ; gap : 8 px }
. row { display : flex ; justify - content : space - between ; padding : 6 px 0 ; border - bottom : 1 px solid # 1 a1a30 }
. row : last - child { border : none }
. label { color : # 8888 aa ; font - size : 13 px }
. val { color : # fff ; font - weight : 600 ; font - size : 13 px }
. ok { color : # 10 b981 } . warn { color : # f59e0b } . bad { color : # ef4444 }
. bar { height : 8 px ; border - radius : 4 px ; background : # 1 a1a30 ; margin - top : 4 px }
. bar - fill { height : 100 % ; border - radius : 4 px ; transition : width . 5 s }
. btn { background : linear - gradient ( 135 deg , # 00 a8ff , # 00 d4ff ) ; color : # 000 ; border : none ; padding : 10 px 20 px ; border - radius : 8 px ; cursor : pointer ; font - weight : 700 ; font - size : 13 px ; margin - top : 12 px }
. btn : hover { opacity : . 9 }
. btn . danger { background : linear - gradient ( 135 deg , # ff4444 , # ff6666 ) }
. loading { color : # 8888 aa ; font - style : italic }
# error { color : # ef4444 ; margin : 12 px 0 ; display : none }
< / s t y l e >
< / h e a d > < b o d y >
< h1 > 🖥 Workspace Status < / h 1 >
< div id = "error" > < / d i v >
< div id = "content" > < p class = "loading" > Loading workspace data ... < / p > < / d i v >
< script >
const token = $ { JSON . stringify ( token ) } ;
async function load ( ) {
try {
const r = await fetch ( 'https://gositeme.com/api/alfred-ide-workspace.php?action=status' , {
headers : token ? { 'Authorization' : 'Bearer ' + token } : { } ,
credentials : 'include'
} ) ;
if ( ! r . ok ) throw new Error ( 'HTTP ' + r . status ) ;
const d = await r . json ( ) ;
render ( d ) ;
} catch ( e ) {
document . getElementById ( 'error' ) . style . display = 'block' ;
document . getElementById ( 'error' ) . textContent = 'Failed to load: ' + e . message ;
document . getElementById ( 'content' ) . innerHTML = '' ;
}
}
function render ( d ) {
const ws = d . workspace || { } ;
const sv = d . server || { } ;
const ide = d . ide || { } ;
const pm2 = d . pm2 || null ;
const bk = d . backup || null ;
const diskPct = sv . disk _used _pct || 0 ;
const diskColor = diskPct > 80 ? '#ef4444' : diskPct > 60 ? '#f59e0b' : '#10b981' ;
const memPct = sv . mem _total _gb ? Math . round ( ( 1 - sv . mem _avail _gb / sv . mem _total _gb ) * 100 ) : 0 ;
const memColor = memPct > 80 ? '#ef4444' : memPct > 60 ? '#f59e0b' : '#10b981' ;
const ideStatus = ide . status === 'online' ? '<span class="ok">● Online</span>' : '<span class="bad">● ' + ( ide . status || 'unknown' ) + '</span>' ;
let html = '<div class="card"><h2>👤 Session</h2>' ;
html += row ( 'User' , ws . user ) ;
html += row ( 'Role' , ws . role === 'commander' ? '⭐ Commander' : 'Customer' ) ;
html += row ( 'Session Expires' , ws . session _expires ? new Date ( ws . session _expires ) . toLocaleString ( ) : 'N/A' ) ;
html += '</div>' ;
html += '<div class="card"><h2>⚡ IDE Service</h2>' ;
html += row ( 'Status' , ideStatus ) ;
html += row ( 'Memory' , ide . memory _mb ? ide . memory _mb + ' MB' : 'N/A' ) ;
html += row ( 'CPU' , ide . cpu != null ? ide . cpu + '%' : 'N/A' ) ;
html += row ( 'Restarts' , ide . restarts != null ? ide . restarts : 'N/A' ) ;
html += row ( 'PID' , ide . pid || 'N/A' ) ;
html += '</div>' ;
html += '<div class="card"><h2>💾 Server</h2>' ;
html += row ( 'Disk' , sv . disk _used _pct + '% used (' + ( sv . disk _total _gb - sv . disk _free _gb ) . toFixed ( 0 ) + ' / ' + sv . disk _total _gb + ' GB)' ) ;
html += '<div class="bar"><div class="bar-fill" style="width:' + diskPct + '%;background:' + diskColor + '"></div></div>' ;
html += row ( 'Memory' , memPct + '% used (' + ( sv . mem _total _gb - sv . mem _avail _gb ) . toFixed ( 1 ) + ' / ' + sv . mem _total _gb + ' GB)' ) ;
html += '<div class="bar"><div class="bar-fill" style="width:' + memPct + '%;background:' + memColor + '"></div></div>' ;
html += row ( 'Load' , sv . load _1m + ' / ' + sv . load _5m ) ;
html += row ( 'Uptime' , sv . uptime _days + ' days' ) ;
html += '</div>' ;
if ( pm2 ) {
html += '<div class="card"><h2>🔧 PM2 Services</h2>' ;
html += row ( 'Online' , '<span class="ok">' + pm2 . online + '</span>' ) ;
html += row ( 'Stopped' , pm2 . stopped > 0 ? '<span class="warn">' + pm2 . stopped + '</span>' : '0' ) ;
html += row ( 'Errored' , pm2 . errored > 0 ? '<span class="bad">' + pm2 . errored + '</span>' : '0' ) ;
if ( pm2 . critical ) {
html += '<div style="margin-top:8px;font-size:12px;color:#8888aa">Critical Services:</div>' ;
for ( const [ n , s ] of Object . entries ( pm2 . critical ) ) {
const cls = s === 'online' ? 'ok' : 'bad' ;
html += '<div class="row"><span class="label">' + n + '</span><span class="val ' + cls + '">' + s + '</span></div>' ;
}
}
html += '</div>' ;
}
if ( bk ) {
html += '<div class="card"><h2>📦 Backup</h2>' ;
html += row ( 'Last Success' , bk . last _success || 'Never' ) ;
html += row ( 'Age' , bk . age _hours != null ? bk . age _hours + ' hours' : 'N/A' ) ;
html += row ( 'Health' , bk . healthy ? '<span class="ok">✓ Healthy</span>' : '<span class="bad">✗ Stale (>48h)</span>' ) ;
html += '</div>' ;
}
if ( ws . role === 'commander' ) {
html += '<button class="btn danger" onclick="restartIDE()">⟳ Restart IDE Service</button>' ;
}
document . getElementById ( 'content' ) . innerHTML = html ;
}
function row ( l , v ) { return '<div class="row"><span class="label">' + l + '</span><span class="val">' + v + '</span></div>' ; }
async function restartIDE ( ) {
if ( ! confirm ( 'Restart Alfred IDE service?' ) ) return ;
try {
const r = await fetch ( 'https://gositeme.com/api/alfred-ide-workspace.php?action=restart' , {
method : 'POST' ,
headers : token ? { 'Authorization' : 'Bearer ' + token } : { } ,
credentials : 'include'
} ) ;
const d = await r . json ( ) ;
alert ( d . success ? 'IDE restarted successfully' : 'Restart may have failed: ' + ( d . output || '' ) ) ;
} catch ( e ) { alert ( 'Error: ' + e . message ) ; }
}
load ( ) ;
setInterval ( load , 30000 ) ;
< / s c r i p t >
< / b o d y > < / h t m l > ` ;
}
module . exports = { activate , deactivate } ;