From 0f27e519ede93fe425964f0817a30d30ce1c5ad6 Mon Sep 17 00:00:00 2001 From: onderakbulut Date: Sun, 31 Aug 2025 16:08:20 +0300 Subject: [PATCH 1/7] Add Windows notification sound feature and related settings --- .vscode/settings.json | 3 ++- package-lock.json | 4 ++-- package.json | 5 +++++ src/extension.ts | 44 ++++++++++++++++++++++++++++++++++++++++++- src/script.ts | 5 ++++- src/ui.ts | 13 +++++++++++++ 6 files changed, 69 insertions(+), 5 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index afdab66..6d1bc3f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,6 @@ "out": true // set this to false to include "out" folder in search results }, // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off" + "typescript.tsc.autoDetect": "off", + "claudeCodeChat.permissions.yoloMode": true } diff --git a/package-lock.json b/package-lock.json index 82a87fc..1e3d15a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "claude-code-chat", - "version": "1.0.0", + "version": "1.0.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claude-code-chat", - "version": "1.0.0", + "version": "1.0.6", "license": "SEE LICENSE IN LICENSE", "devDependencies": { "@types/mocha": "^10.0.10", diff --git a/package.json b/package.json index 52fd8f2..2e6e4f8 100644 --- a/package.json +++ b/package.json @@ -185,6 +185,11 @@ "type": "boolean", "default": false, "description": "Enable Yolo Mode to skip all permission checks. Use with caution as Claude can execute any command without asking." + }, + "claudeCodeChat.notifications.windowsSound": { + "type": "boolean", + "default": false, + "description": "Enable Windows system notification sound when Claude completes a response" } } } diff --git a/src/extension.ts b/src/extension.ts index b27e2f5..34acb2f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -804,6 +804,9 @@ class ClaudeChatProvider { this._isProcessing = false; + // Play Windows notification sound when Claude completes a response + void this._playWindowsNotificationSound(); + // Capture session ID from final result if (jsonData.session_id) { const isNewSession = !this._currentSessionId; @@ -2147,7 +2150,8 @@ class ClaudeChatProvider { 'wsl.distro': config.get('wsl.distro', 'Ubuntu'), 'wsl.nodePath': config.get('wsl.nodePath', '/usr/bin/node'), 'wsl.claudePath': config.get('wsl.claudePath', '/usr/local/bin/claude'), - 'permissions.yoloMode': config.get('permissions.yoloMode', false) + 'permissions.yoloMode': config.get('permissions.yoloMode', false), + 'notifications.windowsSound': config.get('notifications.windowsSound', false) }; this._postMessage({ @@ -2178,6 +2182,41 @@ class ClaudeChatProvider { this._draftMessage = text || ''; } + private async _playWindowsNotificationSound(): Promise { + try { + // Only play sound on Windows platform + if (process.platform !== 'win32') { + return; + } + + // Check if Windows notification sound is enabled + const config = vscode.workspace.getConfiguration('claudeCodeChat'); + const soundEnabled = config.get('notifications.windowsSound', false); + + if (!soundEnabled) { + return; + } + + // Try to play Windows Generic notification sound + try { + const command = 'powershell.exe -Command "(New-Object Media.SoundPlayer \\"C:\\Windows\\Media\\Windows Notify System Generic.wav\\").PlaySync()"'; + await exec(command); + } catch (error1: any) { + // Fallback to alternative Windows notification sound + try { + const fallbackCommand = 'rundll32 user32.dll,MessageBeep 64'; + await exec(fallbackCommand); + } catch (error2: any) { + // Silent fail - don't interrupt user workflow + console.log('Windows notification sound failed to play'); + } + } + } catch (error: any) { + // Silent fail - don't interrupt user workflow + console.log('Windows notification sound error:', error.message); + } + } + private async _updateSettings(settings: { [key: string]: any }): Promise { const config = vscode.workspace.getConfiguration('claudeCodeChat'); @@ -2186,6 +2225,9 @@ class ClaudeChatProvider { if (key === 'permissions.yoloMode') { // YOLO mode is workspace-specific await config.update(key, value, vscode.ConfigurationTarget.Workspace); + } else if (key === 'notifications.windowsSound') { + // Windows sound notification is workspace-specific + await config.update(key, value, vscode.ConfigurationTarget.Workspace); } else { // Other settings are global (user-wide) await config.update(key, value, vscode.ConfigurationTarget.Global); diff --git a/src/script.ts b/src/script.ts index 871f6ab..000610b 100644 --- a/src/script.ts +++ b/src/script.ts @@ -2714,6 +2714,7 @@ const getScript = (isTelemetryEnabled: boolean) => `` -export default getScript; \ No newline at end of file +export default getScript; diff --git a/src/skills-script.ts b/src/skills-script.ts new file mode 100644 index 0000000..5fc8ec1 --- /dev/null +++ b/src/skills-script.ts @@ -0,0 +1,288 @@ +const getSkillsScript = () => ` + // ─── Skills ─── + var skillsSearchTimeout = null; + var skillsCache = null; + var topSkills = (window.__topSkills || []); + + function showSkillsModal() { + sendStats('Skills modal opened'); + document.getElementById('skillsModal').style.display = 'flex'; + loadInstalledSkills(); + if (topSkills.length > 0) { + renderFeaturedSkills(topSkills); + } + } + + function renderFeaturedSkills(skills) { + var grid = document.getElementById('skillsGrid'); + if (!grid) return; + var html = ''; + skills.forEach(function(skill) { + var name = skill.name || 'Unknown'; + var installs = skill.installs || 0; + var source = skill.source || ''; + var installsHtml = installs > 0 ? '' + (installs >= 1000 ? (Math.round(installs / 100) / 10) + 'k' : installs) + ' installs' : ''; + var safeId = escapeHtml(skill.id || name).replace(/'/g, '''); + + var rawUrl = skill.rawUrl || ''; + var installsText = installs >= 1000 ? (Math.round(installs / 100) / 10) + 'k installs' : (installs > 0 ? installs + ' installs' : ''); + html += '
' + + '
' + + '
' + escapeHtml(name.charAt(0).toUpperCase()) + '
' + + '
' + + '
' + escapeHtml(name) + '
' + + '
' + installsHtml + '
' + + '
' + + '
' + + '
' + escapeHtml(source) + '
' + + '
'; + }); + grid.innerHTML = html; + } + + function hideSkillsModal() { + document.getElementById('skillsModal').style.display = 'none'; + } + + function loadInstalledSkills() { + vscode.postMessage({ type: 'loadSkills' }); + } + + function displaySkills(skills) { + var skillsList = document.getElementById('skillsList'); + skillsList.innerHTML = ''; + + if (!skills || skills.length === 0) { + skillsList.innerHTML = '
' + + '
' + + '
No skills installed
' + + '' + + '
'; + return; + } + + skills.forEach(function(skill, idx) { + var item = document.createElement('div'); + item.className = 'mcp-server-item'; + item.style.flexDirection = 'column'; + item.style.alignItems = 'stretch'; + var desc = skill.description || 'No description'; + var content = skill.content || ''; + var detailId = 'skill-detail-' + idx; + item.innerHTML = '
' + + '
' + + '
' + escapeHtml(skill.name) + ' ' + escapeHtml(skill.scope) + '
' + + '
' + escapeHtml(desc) + '
' + + '
' + + '
' + + '' + + '' + + '
' + + '
' + + ''; + skillsList.appendChild(item); + }); + + // Add create button at bottom + var addDiv = document.createElement('div'); + addDiv.className = 'mcp-add-server'; + addDiv.innerHTML = ''; + skillsList.appendChild(addDiv); + } + + function showSkillAddForm() { + document.getElementById('skillsList').style.display = 'none'; + document.getElementById('skillsMarketplace').style.display = 'none'; + document.getElementById('skillAddForm').style.display = 'block'; + // Clear form + document.getElementById('skillName').value = ''; + document.getElementById('skillDescription').value = ''; + document.getElementById('skillContent').value = ''; + document.getElementById('skillName').disabled = false; + } + + function hideSkillAddForm() { + document.getElementById('skillsList').style.display = ''; + document.getElementById('skillsMarketplace').style.display = 'block'; + document.getElementById('skillAddForm').style.display = 'none'; + loadInstalledSkills(); + } + + function saveSkill() { + var name = document.getElementById('skillName').value.trim(); + var description = document.getElementById('skillDescription').value.trim(); + var scope = document.getElementById('skillScope').value; + var content = document.getElementById('skillContent').value; + + if (!name) return; + + // Build SKILL.md content + var skillMd = '---\\n'; + skillMd += 'name: ' + name + '\\n'; + if (description) { + skillMd += 'description: ' + description + '\\n'; + } + skillMd += '---\\n\\n'; + skillMd += content || ''; + + vscode.postMessage({ + type: 'saveSkill', + name: name, + scope: scope, + content: skillMd + }); + + hideSkillAddForm(); + } + + function deleteSkill(name, scope) { + vscode.postMessage({ + type: 'deleteSkill', + name: name, + scope: scope + }); + } + + function searchSkills(query) { + clearTimeout(skillsSearchTimeout); + skillsSearchTimeout = setTimeout(function() { + if (!query || query.length < 2) { + renderFeaturedSkills(topSkills); + return; + } + // Filter featured locally first + var q = query.toLowerCase(); + var local = topSkills.filter(function(s) { + return (s.name && s.name.toLowerCase().indexOf(q) >= 0) || + (s.source && s.source.toLowerCase().indexOf(q) >= 0); + }); + if (local.length > 0) { + renderFeaturedSkills(local); + } else { + var grid = document.getElementById('skillsGrid'); + grid.innerHTML = '
Searching...
'; + } + // Also search API + vscode.postMessage({ type: 'searchSkills', query: query }); + }, 300); + } + + function handleSkillsSearchResponse(data) { + var grid = document.getElementById('skillsGrid'); + if (!grid) return; + + var skills = data.skills || []; + if (skills.length === 0) { + grid.innerHTML = '
No skills found.
'; + return; + } + + var html = ''; + skills.forEach(function(skill) { + var name = skill.name || skill.skillId || 'Unknown'; + var installs = skill.installs || 0; + var source = skill.source || ''; + var safeId = escapeHtml(skill.id || name).replace(/'/g, '''); + + var installsHtml = installs > 0 ? '' + (installs >= 1000 ? (Math.round(installs / 100) / 10) + 'k' : installs) + ' installs' : ''; + + var rawUrl = skill.rawUrl || ''; + var installsText = installs >= 1000 ? (Math.round(installs / 100) / 10) + 'k installs' : (installs > 0 ? installs + ' installs' : ''); + html += '
' + + '
' + + '
' + escapeHtml(name.charAt(0).toUpperCase()) + '
' + + '
' + + '
' + escapeHtml(name) + '
' + + '
' + installsHtml + '
' + + '
' + + '
' + + '
' + escapeHtml(source) + '
' + + '
'; + }); + grid.innerHTML = html; + } + + var skillsDisplayedList = null; + + function installSkillFromMarketplace(el) { + var source = el.dataset.skillSource; + var name = el.dataset.skillName; + var installs = el.dataset.skillInstalls || ''; + + if (!source || !name) return; + + var repoUrl = 'https://github.com/' + source.replace(/^github\\//, ''); + var installsHtml = installs ? '' + installs + '' : ''; + + var grid = document.getElementById('skillsGrid'); + // Save current grid content to restore on back + skillsDisplayedList = grid.innerHTML; + + grid.innerHTML = '
' + + '' + + '
' + + '
' + escapeHtml(name.charAt(0).toUpperCase()) + '
' + + '
' + + '
' + escapeHtml(name) + '
' + + '
' + + installsHtml + + 'GitHub' + + '
' + + '
' + + '
' + + '
' + escapeHtml('Source: ' + source) + '
' + + '
' + + '
Install to
' + + '
' + + '' + + '
' + + '
' + + '
' + + '' + + '
Opens a terminal running npx skills add via skills.sh
' + + '
' + + '
'; + } + + function backToSkillsList() { + var grid = document.getElementById('skillsGrid'); + if (skillsDisplayedList) { + grid.innerHTML = skillsDisplayedList; + } else { + renderFeaturedSkills(topSkills); + } + } + + function toggleSkillDetail(id) { + var el = document.getElementById(id); + if (!el) return; + el.style.display = el.style.display === 'none' ? 'block' : 'none'; + } + + function confirmSkillInstall(btn) { + var source = btn.dataset.source; + var name = btn.dataset.name; + sendStats('Skill installed', { name: name, source: source }); + var scope = document.getElementById('skillInstallScope').value; + + var repoUrl = 'https://github.com/' + source.replace(/^github\\//, ''); + var command = 'npx -y skills add ' + repoUrl + ' --skill ' + name + ' --agent claude-code -y'; + if (scope === 'global') { + command += ' --global'; + } + + vscode.postMessage({ + type: 'runTerminalCommand', + command: command + }); + + hideSkillsModal(); + } +`; + +export default getSkillsScript; diff --git a/src/skills-ui.ts b/src/skills-ui.ts new file mode 100644 index 0000000..be530d0 --- /dev/null +++ b/src/skills-ui.ts @@ -0,0 +1,51 @@ +const getSkillsHtml = () => ` + + +`; + +export default getSkillsHtml; diff --git a/src/test/downloader.integration.test.ts b/src/test/downloader.integration.test.ts new file mode 100644 index 0000000..1f508d5 --- /dev/null +++ b/src/test/downloader.integration.test.ts @@ -0,0 +1,203 @@ +// End-to-end integration tests for the claude downloader. +// +// These hit the REAL npm registry and REAL Anthropic CDN and download the REAL +// native binary to a temp directory. They are slow (~60MB–213MB of transfer) +// and network-dependent. If the suite is ever run in a CI environment without +// egress, these will fail with NETWORK β€” mark them .skip() if you need to. +// +// We never EXECUTE the downloaded binary. We just verify: +// - the downloader returns a sensible result +// - the file exists at the expected path with mode 755 (on Unix) +// - the file starts with a platform-appropriate executable magic number +// - the integrity hash matched (implicit β€” the downloader would throw if not) + +import * as assert from 'assert'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { detectPlatform, downloadClaude, DownloaderError } from '../claudeDownloader'; + +const INTEGRATION_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes β€” 213MB on slow networks + +function mkTempDir(prefix: string): string { + return fs.mkdtempSync(path.join(os.tmpdir(), prefix + '-')); +} + +function rmRf(dir: string): void { + try { + fs.rmSync(dir, { recursive: true, force: true }); + } catch { /* best effort */ } +} + +// Check the first few bytes of the binary match the expected executable format. +// We never parse further than the magic β€” just enough to confirm we wrote out an +// actual executable and not e.g. an HTML error page or a README. +function assertExecutableMagic(binaryPath: string): void { + const fd = fs.openSync(binaryPath, 'r'); + const buf = Buffer.alloc(4); + fs.readSync(fd, buf, 0, 4, 0); + fs.closeSync(fd); + + if (process.platform === 'darwin') { + // Mach-O magic numbers: MH_MAGIC_64 (0xFEEDFACF) or MH_CIGAM_64 (0xCFFAEDFE) + // or fat universal binary (0xCAFEBABE / 0xBEBAFECA). + const m = buf.readUInt32BE(0); + const lm = buf.readUInt32LE(0); + assert.ok( + m === 0xFEEDFACF || m === 0xCAFEBABE || lm === 0xFEEDFACF || lm === 0xCAFEBABE, + `Not a Mach-O binary β€” got magic 0x${m.toString(16)} (LE 0x${lm.toString(16)})`, + ); + } else if (process.platform === 'linux') { + // ELF: 0x7F 'E' 'L' 'F' + assert.strictEqual(buf[0], 0x7F); + assert.strictEqual(buf[1], 0x45); + assert.strictEqual(buf[2], 0x4C); + assert.strictEqual(buf[3], 0x46); + } else if (process.platform === 'win32') { + // PE: starts with MZ (DOS stub). + assert.strictEqual(buf[0], 0x4D); + assert.strictEqual(buf[1], 0x5A); + } +} + +suite('claudeDownloader: integration (real network)', function () { + // Skip entirely on unsupported platforms β€” integration only makes sense where + // a binary is published for us. + const platform = detectPlatform(); + if (!platform) { + test.skip('no supported binary for this platform', () => { /* skipped */ }); + return; + } + + this.timeout(INTEGRATION_TIMEOUT_MS); + + let tempDirs: string[] = []; + + teardown(() => { + for (const d of tempDirs) {rmRf(d);} + tempDirs = []; + }); + + test('downloads the real binary from npm and verifies integrity', async () => { + const dest = mkTempDir('claude-dl-npm'); + tempDirs.push(dest); + + const progressPhases: string[] = []; + const result = await downloadClaude({ + destDir: dest, + onProgress: (p) => { + if (!progressPhases.includes(p.phase)) {progressPhases.push(p.phase);} + }, + }); + + // Happy path should go npm, no fallback. + assert.strictEqual(result.source, 'npm'); + assert.ok(/^\d+\.\d+\.\d+/.test(result.version), 'version looks unfamiliar: ' + result.version); + assert.ok(result.bytesDownloaded > 1_000_000, 'tarball was suspiciously small: ' + result.bytesDownloaded); + + // File is at the expected path with correct permissions. + const expectedPath = path.join(dest, platform.binaryName); + assert.strictEqual(result.binaryPath, expectedPath); + assert.ok(fs.existsSync(result.binaryPath)); + const stat = fs.statSync(result.binaryPath); + if (process.platform !== 'win32') { + + assert.strictEqual(stat.mode & 0o777, 0o755, 'expected chmod 755'); + } + assert.ok(stat.size > 50_000_000, 'extracted binary is suspiciously small: ' + stat.size); + + assertExecutableMagic(result.binaryPath); + + // Progress pipeline actually fired phase transitions. + assert.ok(progressPhases.includes('resolving'), 'missing resolving phase'); + assert.ok(progressPhases.includes('downloading'), 'missing downloading phase'); + assert.ok(progressPhases.includes('verifying'), 'missing verifying phase'); + assert.ok(progressPhases.includes('installing'), 'missing installing phase'); + assert.ok(!progressPhases.includes('fallback'), 'fallback phase fired unexpectedly'); + }); + + test('falls back to CDN when npm is unreachable', async () => { + const dest = mkTempDir('claude-dl-fallback'); + tempDirs.push(dest); + + const progressPhases: string[] = []; + const result = await downloadClaude({ + destDir: dest, + // Point npm at a loopback port that actively refuses connections so + // the npm path fails fast (ECONNREFUSED). CDN override is left at + // default so it hits the real Anthropic CDN. + npmRegistry: 'http://127.0.0.1:1', + onProgress: (p) => { + if (!progressPhases.includes(p.phase)) {progressPhases.push(p.phase);} + }, + }); + + assert.strictEqual(result.source, 'cdn'); + assert.ok(/^\d+\.\d+\.\d+/.test(result.version)); + assert.ok(result.bytesDownloaded > 50_000_000, 'CDN serves uncompressed β‰₯50MB: ' + result.bytesDownloaded); + assert.ok(fs.existsSync(result.binaryPath)); + assertExecutableMagic(result.binaryPath); + assert.ok(progressPhases.includes('fallback'), 'expected fallback phase after npm failure'); + }); + + test('AGGREGATE error when both sources are unreachable', async () => { + const dest = mkTempDir('claude-dl-aggregate'); + tempDirs.push(dest); + + let caught: unknown; + try { + await downloadClaude({ + destDir: dest, + npmRegistry: 'http://127.0.0.1:1', + cdnBase: 'http://127.0.0.1:1', + }); + assert.fail('expected both-sources-fail to throw'); + } catch (err) { + caught = err; + } + + assert.ok(caught instanceof DownloaderError, 'expected DownloaderError'); + const e = caught as DownloaderError; + assert.strictEqual(e.code, 'AGGREGATE'); + assert.ok(e.details, 'AGGREGATE should carry details'); + assert.ok(typeof e.details!.npmCode === 'string', 'npmCode should be populated'); + assert.ok(typeof e.details!.cdnCode === 'string', 'cdnCode should be populated'); + // Should not leak any path from the local temp dir. + assert.ok(!e.message.includes(os.homedir()), 'error message leaks home dir'); + assert.ok(!e.message.includes(dest), 'error message leaks temp dir'); + + // Temp file should be cleaned up β€” nothing left in dest except the dir itself. + const entries = fs.readdirSync(dest); + assert.deepStrictEqual(entries, [], 'temp download files were not cleaned up'); + }); + + test('INTEGRITY error when CDN manifest is tampered (simulated via bogus CDN base)', async () => { + const dest = mkTempDir('claude-dl-bad-cdn'); + tempDirs.push(dest); + + // npm still works so we actually need to disable it to force CDN. + // Point CDN at a non-existent but reachable-looking host β€” expect + // NETWORK (DNS failure) bubbled through AGGREGATE, not INTEGRITY. + // This is really just confirming error classification is coherent when + // the CDN hostname resolves but returns nonsense β€” skip the exact + // INTEGRITY path since we'd need to stand up a mock server. This test + // doubles as a sanity check on the AGGREGATE error formatting. + let caught: unknown; + try { + await downloadClaude({ + destDir: dest, + npmRegistry: 'http://127.0.0.1:1', + cdnBase: 'http://127.0.0.1:1', + }); + assert.fail('expected failure'); + } catch (err) { + caught = err; + } + const e = caught as DownloaderError; + assert.strictEqual(e.code, 'AGGREGATE'); + assert.ok( + e.message.includes('npm:') && e.message.includes('cdn:'), + 'AGGREGATE message should name both sources', + ); + }); +}); diff --git a/src/test/downloader.test.ts b/src/test/downloader.test.ts new file mode 100644 index 0000000..0da8385 --- /dev/null +++ b/src/test/downloader.test.ts @@ -0,0 +1,312 @@ +import * as assert from 'assert'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { detectPlatform, DownloaderError, __test__ } from '../claudeDownloader'; + +const { parseOctal, readTarHeader, processTarChunk, errCode, safeProgress } = __test__; + +// ---- Tar fixture builder ----------------------------------------------- +// Build a ustar header block + aligned data. We only populate the fields our +// parser reads: name (0..100), size (124..136), typeflag (156), prefix (345..500). + +function makeHeader(name: string, size: number, opts: { typeFlag?: string; prefix?: string } = {}): Buffer { + const block = Buffer.alloc(512, 0); + block.write(name, 0, 100, 'utf8'); + // Size: octal ASCII, null-terminated, 11 digits + NUL. + const oct = size.toString(8).padStart(11, '0'); + block.write(oct, 124, 11, 'ascii'); + block[135] = 0; + block[156] = (opts.typeFlag || '0').charCodeAt(0); + if (opts.prefix) {block.write(opts.prefix, 345, 155, 'utf8');} + return block; +} + +function makeMalformedSizeHeader(name: string): Buffer { + // Non-octal junk in the size field. + const block = Buffer.alloc(512, 0); + block.write(name, 0, 100, 'utf8'); + block.write('ZZZ', 124, 3, 'ascii'); + block[156] = '0'.charCodeAt(0); + return block; +} + +function paddedData(size: number, fill = 0x41 /* 'A' */): Buffer { + const padded = Math.ceil(size / 512) * 512; + const buf = Buffer.alloc(padded, 0); + for (let i = 0; i < size; i++) {buf[i] = fill;} + return buf; +} + +function buildTarball(entries: Array<{ name: string; data: Buffer; typeFlag?: string; prefix?: string }>): Buffer { + const parts: Buffer[] = []; + for (const e of entries) { + parts.push(makeHeader(e.name, e.data.length, { typeFlag: e.typeFlag, prefix: e.prefix })); + const padded = Math.ceil(e.data.length / 512) * 512; + const padBlock = Buffer.alloc(padded, 0); + e.data.copy(padBlock, 0); + parts.push(padBlock); + } + // End-of-archive: two zero-blocks. + parts.push(Buffer.alloc(1024, 0)); + return Buffer.concat(parts); +} + +function newWriteStream(): { stream: fs.WriteStream; path: string; read(): Buffer } { + const tmp = path.join(os.tmpdir(), 'downloader-test-' + process.pid + '-' + Date.now() + '-' + Math.random().toString(36).slice(2)); + const stream = fs.createWriteStream(tmp); + return { + stream, + path: tmp, + read: () => fs.readFileSync(tmp), + }; +} + +async function flushWriteStream(s: fs.WriteStream): Promise { + return new Promise((resolve, reject) => { + s.end(() => resolve()); + s.on('error', reject); + }); +} + +suite('claudeDownloader: detectPlatform', () => { + test('returns a supported platform shape on the current host', () => { + const p = detectPlatform(); + if (!p) { + // Test suite only runs on supported hosts β€” skip if we land somewhere weird. + return; + } + assert.strictEqual(typeof p.key, 'string'); + assert.ok(p.key.length > 0); + assert.ok( + /^(darwin|linux|win32)-(x64|arm64)(-musl)?$/.test(p.key), + 'unexpected platform key: ' + p.key, + ); + assert.ok(p.binaryName === 'claude' || p.binaryName === 'claude.exe'); + assert.ok(p.tarEntry === 'package/claude' || p.tarEntry === 'package/claude.exe'); + // Windows β†’ .exe, others β†’ no extension + if (process.platform === 'win32') { + assert.strictEqual(p.binaryName, 'claude.exe'); + assert.strictEqual(p.tarEntry, 'package/claude.exe'); + } else { + assert.strictEqual(p.binaryName, 'claude'); + assert.strictEqual(p.tarEntry, 'package/claude'); + } + }); +}); + +suite('claudeDownloader: DownloaderError', () => { + test('exposes code, message, details, cause', () => { + const cause = new Error('underlying'); + const e = new DownloaderError('NETWORK', 'something failed', { status: 503, host: 'example.com' }, cause); + assert.strictEqual(e.code, 'NETWORK'); + assert.strictEqual(e.message, 'something failed'); + assert.deepStrictEqual(e.details, { status: 503, host: 'example.com' }); + assert.strictEqual(e.cause, cause); + assert.strictEqual(e.name, 'DownloaderError'); + assert.ok(e instanceof Error); + }); + + test('details and cause are optional', () => { + const e = new DownloaderError('CANCELLED', 'stop'); + assert.strictEqual(e.details, undefined); + assert.strictEqual(e.cause, undefined); + }); +}); + +suite('claudeDownloader: parseOctal', () => { + test('parses standard octal size', () => { + const buf = Buffer.alloc(12, 0); + buf.write('00000001024', 0, 'ascii'); // 1024 in octal + assert.strictEqual(parseOctal(buf), 0o1024); + }); + + test('parses octal with trailing NUL terminator', () => { + const buf = Buffer.alloc(12, 0); + buf.write('0000100', 0, 'ascii'); + assert.strictEqual(parseOctal(buf), 0o100); + }); + + test('parses octal with trailing space terminator', () => { + const buf = Buffer.from('0000100 \0\0\0\0\0'); + assert.strictEqual(parseOctal(buf), 0o100); + }); + + test('returns 0 for empty buffer', () => { + assert.strictEqual(parseOctal(Buffer.alloc(12, 0)), 0); + }); + + test('returns NaN for non-octal garbage', () => { + const buf = Buffer.from('ZZZ\0\0\0\0\0\0\0\0\0'); + const result = parseOctal(buf); + assert.ok(Number.isNaN(result), 'expected NaN for non-octal input, got ' + result); + }); +}); + +suite('claudeDownloader: readTarHeader', () => { + test('reads a well-formed header', () => { + const hdr = makeHeader('package/claude', 1024); + const parsed = readTarHeader(hdr); + assert.strictEqual(parsed.name, 'package/claude'); + assert.strictEqual(parsed.size, 1024); + assert.strictEqual(parsed.isRegularFile, true); + }); + + test('combines prefix + name for long paths', () => { + const hdr = makeHeader('claude', 512, { prefix: 'package' }); + const parsed = readTarHeader(hdr); + assert.strictEqual(parsed.name, 'package/claude'); + }); + + test('flags non-regular entries (directory)', () => { + const hdr = makeHeader('package/', 0, { typeFlag: '5' }); + const parsed = readTarHeader(hdr); + assert.strictEqual(parsed.isRegularFile, false); + }); + + test('returns size=-1 when size field is garbage (NaN guard)', () => { + const hdr = makeMalformedSizeHeader('package/claude'); + const parsed = readTarHeader(hdr); + assert.strictEqual(parsed.size, -1, 'malformed size must be clamped to -1'); + }); +}); + +suite('claudeDownloader: processTarChunk', () => { + test('extracts a single matching entry', async () => { + const data = paddedData(2000, 0x42 /* 'B' */); + const binary = data.subarray(0, 2000); + const tar = buildTarball([{ name: 'package/claude', data: binary }]); + + const ws = newWriteStream(); + const state = { found: false, bytesWritten: 0, buffer: Buffer.alloc(0), remainingFileBytes: 0, remainingSkipBytes: 0 }; + processTarChunk(state, tar, 'package/claude', ws.stream); + await flushWriteStream(ws.stream); + + const out = ws.read(); + assert.strictEqual(state.found, true); + assert.strictEqual(out.length, 2000); + assert.deepStrictEqual(out, binary); + fs.unlinkSync(ws.path); + }); + + test('skips non-matching entries and still extracts target', async () => { + const decoy = Buffer.from('ignore me'); + const binary = paddedData(1500, 0x43 /* 'C' */).subarray(0, 1500); + const tar = buildTarball([ + { name: 'package/README.md', data: decoy }, + { name: 'package/claude', data: binary }, + { name: 'package/LICENSE', data: Buffer.from('also ignore') }, + ]); + + const ws = newWriteStream(); + const state = { found: false, bytesWritten: 0, buffer: Buffer.alloc(0), remainingFileBytes: 0, remainingSkipBytes: 0 }; + processTarChunk(state, tar, 'package/claude', ws.stream); + await flushWriteStream(ws.stream); + + assert.strictEqual(state.found, true); + assert.deepStrictEqual(ws.read(), binary); + fs.unlinkSync(ws.path); + }); + + test('handles headers split across multiple chunks', async () => { + const binary = paddedData(3000, 0x44 /* 'D' */).subarray(0, 3000); + const tar = buildTarball([ + { name: 'package/README.md', data: Buffer.from('meh') }, + { name: 'package/claude', data: binary }, + ]); + + const ws = newWriteStream(); + const state = { found: false, bytesWritten: 0, buffer: Buffer.alloc(0), remainingFileBytes: 0, remainingSkipBytes: 0 }; + // Drip-feed 137-byte chunks β€” guaranteed to bisect every header + data block. + const chunkSize = 137; + for (let i = 0; i < tar.length; i += chunkSize) { + processTarChunk(state, tar.subarray(i, Math.min(i + chunkSize, tar.length)), 'package/claude', ws.stream); + } + await flushWriteStream(ws.stream); + + assert.strictEqual(state.found, true); + assert.deepStrictEqual(ws.read(), binary); + fs.unlinkSync(ws.path); + }); + + test('sets found=false when target entry is absent', async () => { + const tar = buildTarball([ + { name: 'package/README.md', data: Buffer.from('nope') }, + ]); + + const ws = newWriteStream(); + const state = { found: false, bytesWritten: 0, buffer: Buffer.alloc(0), remainingFileBytes: 0, remainingSkipBytes: 0 }; + processTarChunk(state, tar, 'package/claude', ws.stream); + await flushWriteStream(ws.stream); + + assert.strictEqual(state.found, false); + assert.strictEqual(ws.read().length, 0); + fs.unlinkSync(ws.path); + }); + + test('throws INTEGRITY on malformed size in header', () => { + const bad = makeMalformedSizeHeader('package/evil'); + const endBlocks = Buffer.alloc(1024, 0); + const tar = Buffer.concat([bad, endBlocks]); + + const ws = newWriteStream(); + const state = { found: false, bytesWritten: 0, buffer: Buffer.alloc(0), remainingFileBytes: 0, remainingSkipBytes: 0 }; + assert.throws( + () => processTarChunk(state, tar, 'package/claude', ws.stream), + (err) => err instanceof DownloaderError && err.code === 'INTEGRITY', + ); + ws.stream.destroy(); + try { fs.unlinkSync(ws.path); } catch { /* best effort */ } + }); + + test('stops cleanly at end-of-archive zero block', async () => { + const binary = paddedData(800, 0x45 /* 'E' */).subarray(0, 800); + const tar = buildTarball([{ name: 'package/claude', data: binary }]); + + const ws = newWriteStream(); + const state = { found: false, bytesWritten: 0, buffer: Buffer.alloc(0), remainingFileBytes: 0, remainingSkipBytes: 0 }; + processTarChunk(state, tar, 'package/claude', ws.stream); + await flushWriteStream(ws.stream); + + assert.strictEqual(state.found, true); + assert.deepStrictEqual(ws.read(), binary); + fs.unlinkSync(ws.path); + }); +}); + +suite('claudeDownloader: errCode helper', () => { + test('extracts .code when present', () => { + const err = Object.assign(new Error('x'), { code: 'EACCES' }); + assert.strictEqual(errCode(err, 'FALLBACK'), 'EACCES'); + }); + + test('returns fallback when code is absent', () => { + assert.strictEqual(errCode(new Error('x'), 'FALLBACK'), 'FALLBACK'); + assert.strictEqual(errCode(null, 'FALLBACK'), 'FALLBACK'); + assert.strictEqual(errCode(undefined, 'FALLBACK'), 'FALLBACK'); + assert.strictEqual(errCode('just a string', 'FALLBACK'), 'FALLBACK'); + }); + + test('returns fallback when code is a non-string', () => { + assert.strictEqual(errCode({ code: 123 }, 'FALLBACK'), 'FALLBACK'); + assert.strictEqual(errCode({ code: '' }, 'FALLBACK'), 'FALLBACK'); + }); +}); + +suite('claudeDownloader: safeProgress helper', () => { + test('invokes the callback with the progress payload', () => { + const calls: unknown[] = []; + safeProgress((p) => calls.push(p), { phase: 'resolving' }); + assert.deepStrictEqual(calls, [{ phase: 'resolving' }]); + }); + + test('swallows callback throws without propagating', () => { + assert.doesNotThrow(() => { + safeProgress(() => { throw new Error('boom'); }, { phase: 'downloading' }); + }); + }); + + test('handles undefined callback', () => { + assert.doesNotThrow(() => safeProgress(undefined, { phase: 'verifying' })); + }); +}); diff --git a/src/top-mcp-servers.json b/src/top-mcp-servers.json new file mode 100644 index 0000000..2e8928d --- /dev/null +++ b/src/top-mcp-servers.json @@ -0,0 +1,479 @@ +[ + { + "id": "sequential-thinking", + "name": "Sequential Thinking", + "description": "Step-by-step reasoning capabilities", + "icon": "", + "stars": 0, + "url": "", + "installType": "npm", + "installConfig": { + "type": "stdio", + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-sequential-thinking" + ] + }, + "featured": true + }, + { + "id": "memory", + "name": "Memory", + "description": "Knowledge graph storage", + "icon": "", + "stars": 0, + "url": "", + "installType": "npm", + "installConfig": { + "type": "stdio", + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-memory" + ] + }, + "featured": true + }, + { + "id": "puppeteer", + "name": "Puppeteer", + "description": "Browser automation", + "icon": "", + "stars": 0, + "url": "", + "installType": "npm", + "installConfig": { + "type": "stdio", + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-puppeteer" + ] + }, + "featured": true + }, + { + "id": "fetch", + "name": "Fetch", + "description": "HTTP requests & web scraping", + "icon": "", + "stars": 0, + "url": "", + "installType": "npm", + "installConfig": { + "type": "stdio", + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-fetch" + ] + }, + "featured": true + }, + { + "id": "filesystem", + "name": "Filesystem", + "description": "File operations & management", + "icon": "", + "stars": 0, + "url": "", + "installType": "npm", + "installConfig": { + "type": "stdio", + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-filesystem" + ] + }, + "featured": true + }, + { + "id": "io.github.upstash/context7", + "name": "Context7", + "description": "Up-to-date code docs for any prompt", + "icon": "", + "stars": 0, + "url": "https://github.com/upstash/context7", + "installType": "npm", + "installConfig": { + "type": "stdio", + "command": "npx", + "args": [ + "-y", + "@upstash/context7-mcp" + ], + "env": { + "CONTEXT7_API_KEY": "" + } + }, + "featured": true + }, + { + "id": "com.airtable/mcp", + "name": "Airtable", + "description": "Official Airtable MCP server for managing bases, tables, and records.", + "icon": "", + "stars": 0, + "url": "", + "installType": "http", + "installConfig": { + "type": "http", + "url": "https://mcp.airtable.com/mcp", + "headers": { + "Authorization": "" + } + } + }, + { + "id": "com.apify/mcp", + "name": "Apify", + "description": "Extract data from social media, search engines, maps, e-commerce sites, and any website using thousands of ready-made tools from Apify Store.", + "icon": "", + "stars": 0, + "url": "https://github.com/apify/apify-mcp-server", + "installType": "http", + "installConfig": { + "type": "http", + "url": "https://mcp.apify.com" + } + }, + { + "id": "io.github.browserbase/mcp-server-browserbase", + "name": "Browserbase", + "description": "MCP server for AI web browser automation using Browserbase and Stagehand", + "icon": "", + "stars": 0, + "url": "https://github.com/browserbase/mcp-server-browserbase", + "installType": "npm", + "installConfig": { + "type": "stdio", + "command": "npx", + "args": [ + "-y", + "@browserbasehq/mcp-server-browserbase" + ], + "env": { + "BROWSERBASE_API_KEY": "", + "BROWSERBASE_PROJECT_ID": "", + "GEMINI_API_KEY": "" + } + } + }, + { + "id": "io.github.clerk/mcp-server", + "name": "Clerk", + "description": "Access Clerk authentication docs, SDK snippets, and quickstart guides", + "icon": "", + "stars": 0, + "url": "https://clerk.com/docs/guides/ai/mcp/clerk-mcp-server", + "installType": "http", + "installConfig": { + "type": "http", + "url": "https://mcp.clerk.com/mcp" + } + }, + { + "id": "com.cloudflare.mcp/mcp", + "name": "Cloudflare", + "description": "Cloudflare MCP servers", + "icon": "", + "stars": 0, + "url": "https://github.com/cloudflare/mcp-server-cloudflare", + "installType": "http", + "installConfig": { + "type": "http", + "url": "https://docs.mcp.cloudflare.com/mcp" + } + }, + { + "id": "ai.exa/mcp", + "name": "Exa", + "description": "Web search and code search MCP server powered by Exa", + "icon": "", + "stars": 0, + "url": "https://github.com/exa-labs/exa-mcp-server", + "installType": "http", + "installConfig": { + "type": "http", + "url": "https://mcp.exa.ai/mcp" + } + }, + { + "id": "com.figma/mcp", + "name": "Figma", + "description": "Official Figma MCP server for accessing design files, components, and design context", + "icon": "", + "stars": 0, + "url": "https://help.figma.com/hc/en-us/articles/35281350665623-Figma-MCP-collection-How-to-set-up-the-Figma-remote-MCP-server-preferred", + "installType": "http", + "installConfig": { + "type": "http", + "url": "https://mcp.figma.com/mcp" + } + }, + { + "id": "dev.firecrawl/mcp", + "name": "Firecrawl", + "description": "Web scraping, crawling, search, and structured data extraction powered by Firecrawl.", + "icon": "", + "stars": 0, + "url": "https://github.com/firecrawl/firecrawl-mcp-server", + "installType": "http", + "installConfig": { + "type": "http", + "url": "https://mcp.firecrawl.dev/v2/mcp", + "headers": { + "Authorization": "" + } + } + }, + { + "id": "io.github.github/github-mcp-server", + "name": "GitHub", + "description": "Official GitHub MCP server for repos, issues, PRs, and workflows", + "icon": "", + "stars": 0, + "url": "https://github.com/github/github-mcp-server", + "installType": "http", + "installConfig": { + "type": "http", + "url": "https://api.githubcopilot.com/mcp/" + } + }, + { + "id": "app.linear/linear", + "name": "Linear", + "description": "MCP server for Linear project management and issue tracking", + "icon": "", + "stars": 0, + "url": "", + "installType": "sse", + "installConfig": { + "type": "sse", + "url": "https://mcp.linear.app/sse" + } + }, + { + "id": "com.mux/mcp", + "name": "Mux", + "description": "The official MCP Server for the Mux API", + "icon": "", + "stars": 0, + "url": "https://github.com/muxinc/mux-node-sdk", + "installType": "http", + "installConfig": { + "type": "http", + "url": "https://mcp.mux.com", + "headers": { + "Authorization": "" + } + } + }, + { + "id": "com.neon/mcp", + "name": "Neon", + "description": "Official Neon MCP server for managing Neon projects and Postgres databases.", + "icon": "", + "stars": 0, + "url": "https://github.com/neondatabase/mcp-server-neon", + "installType": "http", + "installConfig": { + "type": "http", + "url": "https://mcp.neon.tech/mcp", + "headers": { + "Authorization": "", + "x-read-only": "" + } + } + }, + { + "id": "com.netlify/mcp", + "name": "Netlify", + "description": "Netlify's official MCP server for builds, deploys, and project management.", + "icon": "", + "stars": 0, + "url": "https://github.com/netlify/netlify-mcp", + "installType": "npm", + "installConfig": { + "type": "stdio", + "command": "npx", + "args": [ + "-y", + "@netlify/mcp" + ], + "env": { + "NETLIFY_PERSONAL_ACCESS_TOKEN": "" + } + } + }, + { + "id": "io.github.vercel/next-devtools-mcp", + "name": "Next.js Devtools", + "description": "Next.js development tools MCP server with stdio transport", + "icon": "", + "stars": 0, + "url": "https://github.com/vercel/next-devtools-mcp", + "installType": "npm", + "installConfig": { + "type": "stdio", + "command": "npx", + "args": [ + "-y", + "next-devtools-mcp" + ] + } + }, + { + "id": "com.notion/mcp", + "name": "Notion", + "description": "Official Notion MCP server", + "icon": "", + "stars": 0, + "url": "", + "installType": "http", + "installConfig": { + "type": "http", + "url": "https://mcp.notion.com/mcp" + } + }, + { + "id": "io.github.railwayapp/mcp-server", + "name": "Railway", + "description": "Official Railway MCP server", + "icon": "", + "stars": 0, + "url": "https://github.com/railwayapp/railway-mcp-server", + "installType": "npm", + "installConfig": { + "type": "stdio", + "command": "npx", + "args": [ + "-y", + "@railway/mcp-server" + ] + } + }, + { + "id": "com.render/mcp", + "name": "Render", + "description": "Official Render MCP server for managing Render resources.", + "icon": "", + "stars": 0, + "url": "https://github.com/render-oss/render-mcp-server", + "installType": "http", + "installConfig": { + "type": "http", + "url": "https://mcp.render.com/mcp", + "headers": { + "Authorization": "" + } + } + }, + { + "id": "com.resend/mcp", + "name": "Resend", + "description": "Official Resend MCP server for email operations and audience management.", + "icon": "", + "stars": 0, + "url": "https://github.com/resend/mcp-send-email", + "installType": "npm", + "installConfig": { + "type": "stdio", + "command": "npx", + "args": [ + "-y", + "resend-mcp" + ], + "env": { + "RESEND_API_KEY": "" + } + } + }, + { + "id": "io.sanity.www/mcp", + "name": "Sanity", + "description": "Direct access to your Sanity projects (content, datasets, releases, schemas) and agent rules", + "icon": "", + "stars": 0, + "url": "https://github.com/sanity-io/agent-toolkit", + "installType": "http", + "installConfig": { + "type": "http", + "url": "https://mcp.sanity.io" + } + }, + { + "id": "io.github.getsentry/sentry-mcp", + "name": "Sentry", + "description": "MCP server for Sentry issue tracking and debugging", + "icon": "", + "stars": 0, + "url": "https://github.com/getsentry/sentry-mcp", + "installType": "npm", + "installConfig": { + "type": "stdio", + "command": "npx", + "args": [ + "-y", + "@sentry/mcp-server" + ], + "env": { + "SENTRY_ACCESS_TOKEN": "" + } + } + }, + { + "id": "com.slack/mcp", + "name": "Slack", + "description": "Official Slack MCP server for search, messaging, canvases, and users.", + "icon": "", + "stars": 0, + "url": "https://github.com/slackapi/slack-mcp-plugin", + "installType": "http", + "installConfig": { + "type": "http", + "url": "https://mcp.slack.com/mcp" + } + }, + { + "id": "com.stripe/mcp", + "name": "Stripe", + "description": "Official Stripe MCP server for Stripe API tools.", + "icon": "", + "stars": 0, + "url": "https://github.com/stripe/agent-toolkit", + "installType": "http", + "installConfig": { + "type": "http", + "url": "https://mcp.stripe.com" + } + }, + { + "id": "com.supabase/mcp", + "name": "Supabase", + "description": "MCP server for interacting with the Supabase platform", + "icon": "", + "stars": 0, + "url": "https://github.com/supabase-community/supabase-mcp", + "installType": "http", + "installConfig": { + "type": "http", + "url": "https://mcp.supabase.com/mcp" + } + }, + { + "id": "com.vercel/vercel-mcp", + "name": "Vercel", + "description": "An MCP server for Vercel", + "icon": "", + "stars": 0, + "url": "https://github.com/vercel/vercel-mcp-overview", + "installType": "http", + "installConfig": { + "type": "http", + "url": "https://mcp.vercel.com" + } + } +] \ No newline at end of file diff --git a/src/top-plugins.json b/src/top-plugins.json new file mode 100644 index 0000000..92ff4a0 --- /dev/null +++ b/src/top-plugins.json @@ -0,0 +1,240 @@ +[ + { + "name": "agent-sdk-dev", + "description": "Claude Agent SDK Development Plugin", + "verified": true, + "type": "official", + "installId": "agent-sdk-dev@claude-plugins-official" + }, + { + "name": "claude-code-setup", + "description": "Analyze codebases and recommend tailored Claude Code automations such as hooks, skills, MCP servers, and subagents.", + "verified": true, + "type": "official", + "installId": "claude-code-setup@claude-plugins-official" + }, + { + "name": "claude-md-management", + "description": "Tools to maintain and improve CLAUDE.md files - audit quality, capture session learnings, and keep project memory current.", + "verified": true, + "type": "official", + "installId": "claude-md-management@claude-plugins-official" + }, + { + "name": "code-review", + "description": "Automated code review for pull requests using multiple specialized agents with confidence-based scoring", + "verified": true, + "type": "official", + "installId": "code-review@claude-plugins-official" + }, + { + "name": "code-simplifier", + "description": "Agent that simplifies and refines code for clarity, consistency, and maintainability while preserving functionality", + "verified": true, + "type": "official", + "installId": "code-simplifier@claude-plugins-official" + }, + { + "name": "commit-commands", + "description": "Streamline your git workflow with simple commands for committing, pushing, and creating pull requests", + "verified": true, + "type": "official", + "installId": "commit-commands@claude-plugins-official" + }, + { + "name": "explanatory-output-style", + "description": "Adds educational insights about implementation choices and codebase patterns (mimics the deprecated Explanatory output style)", + "verified": true, + "type": "official", + "installId": "explanatory-output-style@claude-plugins-official" + }, + { + "name": "feature-dev", + "description": "Comprehensive feature development workflow with specialized agents for codebase exploration, architecture design, and quality review", + "verified": true, + "type": "official", + "installId": "feature-dev@claude-plugins-official" + }, + { + "name": "frontend-design", + "description": "Frontend design skill for UI/UX implementation", + "verified": true, + "type": "official", + "installId": "frontend-design@claude-plugins-official" + }, + { + "name": "hookify", + "description": "Easily create hooks to prevent unwanted behaviors by analyzing conversation patterns", + "verified": true, + "type": "official", + "installId": "hookify@claude-plugins-official" + }, + { + "name": "learning-output-style", + "description": "Interactive learning mode that requests meaningful code contributions at decision points (mimics the unshipped Learning output style)", + "verified": true, + "type": "official", + "installId": "learning-output-style@claude-plugins-official" + }, + { + "name": "math-olympiad", + "description": "Solve competition math (IMO, Putnam, USAMO) with adversarial verification that catches what self-verification misses. Fresh-context verifiers attack proofs with specific failure patterns. Calibrated abstention over bluffing.", + "verified": true, + "type": "official", + "installId": "math-olympiad@claude-plugins-official" + }, + { + "name": "mcp-server-dev", + "description": "Skills for designing and building MCP servers that work seamlessly with Claude \u2014 guides you through deployment models (remote HTTP, MCPB, local), tool design patterns, auth, and interactive MCP apps.", + "verified": true, + "type": "official", + "installId": "mcp-server-dev@claude-plugins-official" + }, + { + "name": "playground", + "description": "Creates interactive HTML playgrounds \u2014 self-contained single-file explorers with visual controls, live preview, and prompt output with copy button", + "verified": true, + "type": "official", + "installId": "playground@claude-plugins-official" + }, + { + "name": "plugin-dev", + "description": "Plugin development toolkit with skills for creating agents, commands, hooks, MCP integrations, and comprehensive plugin structure guidance", + "verified": true, + "type": "official", + "installId": "plugin-dev@claude-plugins-official" + }, + { + "name": "pr-review-toolkit", + "description": "Comprehensive PR review agents specializing in comments, tests, error handling, type design, code quality, and code simplification", + "verified": true, + "type": "official", + "installId": "pr-review-toolkit@claude-plugins-official" + }, + { + "name": "ralph-loop", + "description": "Continuous self-referential AI loops for interactive iterative development, implementing the Ralph Wiggum technique. Run Claude in a while-true loop with the same prompt until task completion.", + "verified": true, + "type": "official", + "installId": "ralph-loop@claude-plugins-official" + }, + { + "name": "security-guidance", + "description": "Security reminder hook that warns about potential security issues when editing files, including command injection, XSS, and unsafe code patterns", + "verified": true, + "type": "official", + "installId": "security-guidance@claude-plugins-official" + }, + { + "name": "skill-creator", + "description": "Create new skills, improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, update or optimize an existing skill, run evals to test a skill, or benchmark skill performance with variance analysis.", + "verified": true, + "type": "official", + "installId": "skill-creator@claude-plugins-official" + }, + { + "name": "asana", + "description": "Asana project management integration. Create and manage tasks, search projects, update assignments, track progress, and integrate your development workflow with Asana's work management platform.", + "verified": false, + "type": "external", + "installId": "asana@claude-plugins-official" + }, + { + "name": "context7", + "description": "Upstash Context7 MCP server for up-to-date documentation lookup. Pull version-specific documentation and code examples directly from source repositories into your LLM context.", + "verified": false, + "type": "external", + "installId": "context7@claude-plugins-official" + }, + { + "name": "discord", + "description": "Discord channel for Claude Code \u2014 messaging bridge with built-in access control. Manage pairing, allowlists, and policy via /discord:access.", + "verified": false, + "type": "external", + "installId": "discord@claude-plugins-official" + }, + { + "name": "fakechat", + "description": "Localhost iMessage-style web chat for Claude Code \u2014 test surface with file upload and edits. No tokens, no access control.", + "verified": false, + "type": "external", + "installId": "fakechat@claude-plugins-official" + }, + { + "name": "firebase", + "description": "Google Firebase MCP integration. Manage Firestore databases, authentication, cloud functions, hosting, and storage. Build and manage your Firebase backend directly from your development workflow.", + "verified": false, + "type": "external", + "installId": "firebase@claude-plugins-official" + }, + { + "name": "github", + "description": "Official GitHub MCP server for repository management. Create issues, manage pull requests, review code, search repositories, and interact with GitHub's full API directly from Claude Code.", + "verified": false, + "type": "external", + "installId": "github@claude-plugins-official" + }, + { + "name": "gitlab", + "description": "GitLab DevOps platform integration. Manage repositories, merge requests, CI/CD pipelines, issues, and wikis. Full access to GitLab's comprehensive DevOps lifecycle tools.", + "verified": false, + "type": "external", + "installId": "gitlab@claude-plugins-official" + }, + { + "name": "greptile", + "description": "AI code review agent for GitHub and GitLab. View and resolve Greptile's PR review comments directly from Claude Code.", + "verified": false, + "type": "external", + "installId": "greptile@claude-plugins-official" + }, + { + "name": "laravel-boost", + "description": "Laravel development toolkit MCP server. Provides intelligent assistance for Laravel applications including Artisan commands, Eloquent queries, routing, migrations, and framework-specific code generation.", + "verified": false, + "type": "external", + "installId": "laravel-boost@claude-plugins-official" + }, + { + "name": "linear", + "description": "Linear issue tracking integration. Create issues, manage projects, update statuses, search across workspaces, and streamline your software development workflow with Linear's modern issue tracker.", + "verified": false, + "type": "external", + "installId": "linear@claude-plugins-official" + }, + { + "name": "playwright", + "description": "Browser automation and end-to-end testing MCP server by Microsoft. Enables Claude to interact with web pages, take screenshots, fill forms, click elements, and perform automated browser testing workflows.", + "verified": false, + "type": "external", + "installId": "playwright@claude-plugins-official" + }, + { + "name": "serena", + "description": "Semantic code analysis MCP server providing intelligent code understanding, refactoring suggestions, and codebase navigation through language server protocol integration.", + "verified": false, + "type": "external", + "installId": "serena@claude-plugins-official" + }, + { + "name": "slack", + "description": "Slack workspace integration. Search messages, access channels, read threads, and stay connected with your team's communications while coding. Find relevant discussions and context quickly.", + "verified": false, + "type": "external", + "installId": "slack@claude-plugins-official" + }, + { + "name": "supabase", + "description": "Supabase MCP integration for database operations, authentication, storage, and real-time subscriptions. Manage your Supabase projects, run SQL queries, and interact with your backend directly.", + "verified": false, + "type": "external", + "installId": "supabase@claude-plugins-official" + }, + { + "name": "telegram", + "description": "Telegram channel for Claude Code \u2014 messaging bridge with built-in access control. Manage pairing, allowlists, and policy via /telegram:access.", + "verified": false, + "type": "external", + "installId": "telegram@claude-plugins-official" + } +] \ No newline at end of file diff --git a/src/top-skills.json b/src/top-skills.json new file mode 100644 index 0000000..37bd948 --- /dev/null +++ b/src/top-skills.json @@ -0,0 +1,289 @@ +[ + { + "id": "vercel-labs/skills/find-skills", + "name": "find-skills", + "installs": 654260, + "source": "vercel-labs/skills", + "rawUrl": "https://raw.githubusercontent.com/vercel-labs/skills/main/skills/find-skills/SKILL.md" + }, + { + "id": "vercel-labs/agent-skills/vercel-react-best-practices", + "name": "vercel-react-best-practices", + "installs": 234225, + "source": "vercel-labs/agent-skills", + "rawUrl": "https://raw.githubusercontent.com/vercel-labs/agent-skills/main/skills/react-best-practices/SKILL.md" + }, + { + "id": "vercel-labs/agent-skills/web-design-guidelines", + "name": "web-design-guidelines", + "installs": 187122, + "source": "vercel-labs/agent-skills", + "rawUrl": "https://raw.githubusercontent.com/vercel-labs/agent-skills/main/skills/web-design-guidelines/SKILL.md" + }, + { + "id": "anthropics/skills/frontend-design", + "name": "frontend-design", + "installs": 184608, + "source": "anthropics/skills", + "rawUrl": "https://raw.githubusercontent.com/anthropics/skills/main/skills/frontend-design/SKILL.md" + }, + { + "id": "vercel-labs/agent-browser/agent-browser", + "name": "agent-browser", + "installs": 119125, + "source": "vercel-labs/agent-browser", + "rawUrl": "https://raw.githubusercontent.com/vercel-labs/agent-browser/main/skills/agent-browser/SKILL.md" + }, + { + "id": "anthropics/skills/skill-creator", + "name": "skill-creator", + "installs": 97605, + "source": "anthropics/skills", + "rawUrl": "https://raw.githubusercontent.com/anthropics/skills/main/skills/skill-creator/SKILL.md" + }, + { + "id": "nextlevelbuilder/ui-ux-pro-max-skill/ui-ux-pro-max", + "name": "ui-ux-pro-max", + "installs": 74564, + "source": "nextlevelbuilder/ui-ux-pro-max-skill", + "rawUrl": "https://raw.githubusercontent.com/nextlevelbuilder/ui-ux-pro-max-skill/main/.claude/skills/ui-ux-pro-max/SKILL.md" + }, + { + "id": "microsoft/azure-skills/microsoft-foundry", + "name": "microsoft-foundry", + "installs": 74376, + "source": "microsoft/azure-skills", + "rawUrl": "https://raw.githubusercontent.com/microsoft/azure-skills/main/skills/microsoft-foundry/SKILL.md" + }, + { + "id": "obra/superpowers/brainstorming", + "name": "brainstorming", + "installs": 66697, + "source": "obra/superpowers", + "rawUrl": "https://raw.githubusercontent.com/obra/superpowers/main/skills/brainstorming/SKILL.md" + }, + { + "id": "browser-use/browser-use/browser-use", + "name": "browser-use", + "installs": 52773, + "source": "browser-use/browser-use", + "rawUrl": "https://raw.githubusercontent.com/browser-use/browser-use/main/skills/browser-use/SKILL.md" + }, + { + "id": "coreyhaines31/marketingskills/seo-audit", + "name": "seo-audit", + "installs": 50157, + "source": "coreyhaines31/marketingskills", + "rawUrl": "https://raw.githubusercontent.com/coreyhaines31/marketingskills/main/skills/seo-audit/SKILL.md" + }, + { + "id": "anthropics/skills/pdf", + "name": "pdf", + "installs": 45709, + "source": "anthropics/skills", + "rawUrl": "https://raw.githubusercontent.com/anthropics/skills/main/skills/pdf/SKILL.md" + }, + { + "id": "supabase/agent-skills/supabase-postgres-best-practices", + "name": "supabase-postgres-best-practices", + "installs": 43862, + "source": "supabase/agent-skills", + "rawUrl": "https://raw.githubusercontent.com/supabase/agent-skills/main/skills/supabase-postgres-best-practices/SKILL.md" + }, + { + "id": "coreyhaines31/marketingskills/copywriting", + "name": "copywriting", + "installs": 42743, + "source": "coreyhaines31/marketingskills", + "rawUrl": "https://raw.githubusercontent.com/coreyhaines31/marketingskills/main/skills/copywriting/SKILL.md" + }, + { + "id": "anthropics/skills/pptx", + "name": "pptx", + "installs": 41526, + "source": "anthropics/skills", + "rawUrl": "https://raw.githubusercontent.com/anthropics/skills/main/skills/pptx/SKILL.md" + }, + { + "id": "vercel-labs/next-skills/next-best-practices", + "name": "next-best-practices", + "installs": 40732, + "source": "vercel-labs/next-skills", + "rawUrl": "https://raw.githubusercontent.com/vercel-labs/next-skills/main/skills/next-best-practices/SKILL.md" + }, + { + "id": "squirrelscan/skills/audit-website", + "name": "audit-website", + "installs": 37654, + "source": "squirrelscan/skills", + "rawUrl": "https://raw.githubusercontent.com/squirrelscan/skills/main/audit-website/SKILL.md" + }, + { + "id": "obra/superpowers/systematic-debugging", + "name": "systematic-debugging", + "installs": 36470, + "source": "obra/superpowers", + "rawUrl": "https://raw.githubusercontent.com/obra/superpowers/main/skills/systematic-debugging/SKILL.md" + }, + { + "id": "anthropics/skills/docx", + "name": "docx", + "installs": 35928, + "source": "anthropics/skills", + "rawUrl": "https://raw.githubusercontent.com/anthropics/skills/main/skills/docx/SKILL.md" + }, + { + "id": "obra/superpowers/writing-plans", + "name": "writing-plans", + "installs": 35010, + "source": "obra/superpowers", + "rawUrl": "https://raw.githubusercontent.com/obra/superpowers/main/skills/writing-plans/SKILL.md" + }, + { + "id": "shadcn/ui/shadcn", + "name": "shadcn", + "installs": 33897, + "source": "shadcn/ui", + "rawUrl": "https://raw.githubusercontent.com/shadcn/ui/main/skills/shadcn/SKILL.md" + }, + { + "id": "anthropics/skills/xlsx", + "name": "xlsx", + "installs": 32936, + "source": "anthropics/skills", + "rawUrl": "https://raw.githubusercontent.com/anthropics/skills/main/skills/xlsx/SKILL.md" + }, + { + "id": "obra/superpowers/using-superpowers", + "name": "using-superpowers", + "installs": 30937, + "source": "obra/superpowers", + "rawUrl": "https://raw.githubusercontent.com/obra/superpowers/main/skills/using-superpowers/SKILL.md" + }, + { + "id": "coreyhaines31/marketingskills/marketing-psychology", + "name": "marketing-psychology", + "installs": 30917, + "source": "coreyhaines31/marketingskills", + "rawUrl": "https://raw.githubusercontent.com/coreyhaines31/marketingskills/main/skills/marketing-psychology/SKILL.md" + }, + { + "id": "obra/superpowers/test-driven-development", + "name": "test-driven-development", + "installs": 30410, + "source": "obra/superpowers", + "rawUrl": "https://raw.githubusercontent.com/obra/superpowers/main/skills/test-driven-development/SKILL.md" + }, + { + "id": "anthropics/skills/webapp-testing", + "name": "webapp-testing", + "installs": 29748, + "source": "anthropics/skills", + "rawUrl": "https://raw.githubusercontent.com/anthropics/skills/main/skills/webapp-testing/SKILL.md" + }, + { + "id": "obra/superpowers/executing-plans", + "name": "executing-plans", + "installs": 28743, + "source": "obra/superpowers", + "rawUrl": "https://raw.githubusercontent.com/obra/superpowers/main/skills/executing-plans/SKILL.md" + }, + { + "id": "obra/superpowers/requesting-code-review", + "name": "requesting-code-review", + "installs": 28421, + "source": "obra/superpowers", + "rawUrl": "https://raw.githubusercontent.com/obra/superpowers/main/skills/requesting-code-review/SKILL.md" + }, + { + "id": "coreyhaines31/marketingskills/content-strategy", + "name": "content-strategy", + "installs": 27875, + "source": "coreyhaines31/marketingskills", + "rawUrl": "https://raw.githubusercontent.com/coreyhaines31/marketingskills/main/skills/content-strategy/SKILL.md" + }, + { + "id": "coreyhaines31/marketingskills/programmatic-seo", + "name": "programmatic-seo", + "installs": 27820, + "source": "coreyhaines31/marketingskills", + "rawUrl": "https://raw.githubusercontent.com/coreyhaines31/marketingskills/main/skills/programmatic-seo/SKILL.md" + }, + { + "id": "coreyhaines31/marketingskills/social-content", + "name": "social-content", + "installs": 26700, + "source": "coreyhaines31/marketingskills", + "rawUrl": "https://raw.githubusercontent.com/coreyhaines31/marketingskills/main/skills/social-content/SKILL.md" + }, + { + "id": "coreyhaines31/marketingskills/product-marketing-context", + "name": "product-marketing-context", + "installs": 25930, + "source": "coreyhaines31/marketingskills", + "rawUrl": "https://raw.githubusercontent.com/coreyhaines31/marketingskills/main/skills/product-marketing-context/SKILL.md" + }, + { + "id": "coreyhaines31/marketingskills/marketing-ideas", + "name": "marketing-ideas", + "installs": 25516, + "source": "coreyhaines31/marketingskills", + "rawUrl": "https://raw.githubusercontent.com/coreyhaines31/marketingskills/main/skills/marketing-ideas/SKILL.md" + }, + { + "id": "roin-orca/skills/simple", + "name": "simple", + "installs": 25467, + "source": "roin-orca/skills", + "rawUrl": "https://raw.githubusercontent.com/roin-orca/skills/main/skills/simple/SKILL.md" + }, + { + "id": "coreyhaines31/marketingskills/pricing-strategy", + "name": "pricing-strategy", + "installs": 25142, + "source": "coreyhaines31/marketingskills", + "rawUrl": "https://raw.githubusercontent.com/coreyhaines31/marketingskills/main/skills/pricing-strategy/SKILL.md" + }, + { + "id": "anthropics/skills/mcp-builder", + "name": "mcp-builder", + "installs": 24764, + "source": "anthropics/skills", + "rawUrl": "https://raw.githubusercontent.com/anthropics/skills/main/skills/mcp-builder/SKILL.md" + }, + { + "id": "obra/superpowers/subagent-driven-development", + "name": "subagent-driven-development", + "installs": 24432, + "source": "obra/superpowers", + "rawUrl": "https://raw.githubusercontent.com/obra/superpowers/main/skills/subagent-driven-development/SKILL.md" + }, + { + "id": "coreyhaines31/marketingskills/copy-editing", + "name": "copy-editing", + "installs": 24073, + "source": "coreyhaines31/marketingskills", + "rawUrl": "https://raw.githubusercontent.com/coreyhaines31/marketingskills/main/skills/copy-editing/SKILL.md" + }, + { + "id": "pbakaus/impeccable/frontend-design", + "name": "frontend-design", + "installs": 23984, + "source": "pbakaus/impeccable", + "rawUrl": "https://raw.githubusercontent.com/pbakaus/impeccable/main/.claude/skills/frontend-design/SKILL.md" + }, + { + "id": "pbakaus/impeccable/polish", + "name": "polish", + "installs": 23360, + "source": "pbakaus/impeccable", + "rawUrl": "https://raw.githubusercontent.com/pbakaus/impeccable/main/.claude/skills/polish/SKILL.md" + }, + { + "id": "google-labs-code/stitch-skills/design-md", + "name": "design-md", + "installs": 19272, + "source": "google-labs-code/stitch-skills", + "rawUrl": "https://raw.githubusercontent.com/google-labs-code/stitch-skills/main/skills/design-md/SKILL.md" + } +] \ No newline at end of file diff --git a/src/ui-styles.ts b/src/ui-styles.ts index 0c33137..76bd766 100644 --- a/src/ui-styles.ts +++ b/src/ui-styles.ts @@ -338,6 +338,206 @@ const styles = ` background-color: rgba(128, 128, 128, 0.05); } + /* AskUserQuestion */ + .ask-user-question { + margin: 4px 12px 20px 12px; + background-color: rgba(0, 122, 204, 0.08); + border: 1px solid rgba(0, 122, 204, 0.3); + border-radius: 8px; + padding: 16px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + animation: slideUp 0.3s ease; + } + + .ask-question-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 12px; + font-weight: 600; + color: var(--vscode-foreground); + } + + .ask-question-header .icon { + font-size: 16px; + } + + .ask-question-content { + font-size: 13px; + line-height: 1.4; + color: var(--vscode-descriptionForeground); + } + + .question-block { + margin-bottom: 16px; + } + + .question-block:last-of-type { + margin-bottom: 12px; + } + + .question-header { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--vscode-descriptionForeground); + margin-bottom: 4px; + } + + .question-text { + font-size: 13px; + font-weight: 500; + color: var(--vscode-foreground); + margin-bottom: 8px; + } + + .question-options { + display: flex; + flex-direction: column; + gap: 4px; + margin-bottom: 6px; + } + + .question-option { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 10px 12px; + border: 1px solid var(--vscode-panel-border); + border-radius: 6px; + cursor: pointer; + transition: all 0.15s ease; + background-color: transparent; + } + + .question-option:hover { + background-color: var(--vscode-list-hoverBackground); + border-color: var(--vscode-focusBorder); + } + + /* Hide native radio/checkbox, use custom styling */ + .question-option input[type="radio"], + .question-option input[type="checkbox"] { + -webkit-appearance: none; + appearance: none; + width: 16px; + height: 16px; + border: 2px solid var(--vscode-descriptionForeground); + background: transparent; + cursor: pointer; + flex-shrink: 0; + margin: 1px 0 0 0; + padding: 0; + transition: all 0.15s ease; + } + + .question-option input[type="radio"] { + border-radius: 50%; + } + + .question-option input[type="checkbox"] { + border-radius: 3px; + } + + .question-option input[type="radio"]:checked { + border-color: var(--vscode-button-background); + background: radial-gradient(circle, var(--vscode-button-background) 40%, transparent 44%); + } + + .question-option input[type="checkbox"]:checked { + border-color: var(--vscode-button-background); + background-color: var(--vscode-button-background); + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='white'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E"); + background-size: 12px; + background-position: center; + background-repeat: no-repeat; + } + + /* Selected option card highlight */ + .question-option:has(input:checked) { + border-color: var(--vscode-button-background); + background-color: rgba(0, 122, 204, 0.08); + } + + .option-content { + display: flex; + flex-direction: column; + gap: 2px; + } + + .option-label { + font-size: 13px; + font-weight: 500; + color: var(--vscode-foreground); + } + + .option-description { + font-size: 12px; + color: var(--vscode-descriptionForeground); + } + + .question-freetext { + margin-top: 6px; + } + + .question-freetext-input { + width: 100%; + padding: 6px 10px; + font-size: 13px; + font-family: var(--vscode-font-family); + background-color: var(--vscode-input-background); + color: var(--vscode-input-foreground); + border: 1px solid var(--vscode-input-border, var(--vscode-panel-border)); + border-radius: 4px; + outline: none; + box-sizing: border-box; + } + + .question-freetext-input:focus { + border-color: var(--vscode-focusBorder); + } + + .question-freetext-input:disabled { + opacity: 0.6; + cursor: not-allowed; + } + + .ask-question-buttons { + margin-top: 8px; + display: flex; + gap: 8px; + justify-content: flex-end; + } + + .ask-question-decision { + font-size: 12px; + padding: 8px 12px; + border-radius: 4px; + margin-top: 8px; + } + + .ask-question-decision.allowed { + background-color: rgba(0, 122, 204, 0.1); + color: var(--vscode-foreground); + border: 1px solid rgba(0, 122, 204, 0.2); + } + + .ask-question-decision.expired { + background-color: rgba(128, 128, 128, 0.15); + color: var(--vscode-descriptionForeground); + border: 1px solid rgba(128, 128, 128, 0.3); + } + + .ask-question-decided { + opacity: 0.7; + pointer-events: none; + } + + .ask-question-decided .ask-question-buttons { + display: none; + } + /* Permissions Management */ .permissions-list { max-height: 300px; @@ -585,6 +785,62 @@ const styles = ` line-height: 1.3; } + .env-variables-list { + display: flex; + flex-direction: column; + gap: 8px; + } + + .env-variable-row { + display: flex; + gap: 8px; + align-items: center; + } + + .env-variable-row input { + flex: 1; + padding: 6px 8px; + border: 1px solid var(--vscode-input-border, var(--vscode-panel-border)); + border-radius: 4px; + background-color: var(--vscode-input-background); + color: var(--vscode-input-foreground); + font-size: 12px; + font-family: monospace; + } + + .env-variable-row input:focus { + outline: none; + border-color: var(--vscode-focusBorder); + } + + .env-variable-row input::placeholder { + color: var(--vscode-input-placeholderForeground); + } + + .env-variable-row .env-key { + flex: 0.4; + } + + .env-variable-row .env-value { + flex: 0.6; + } + + .env-variable-remove { + background: transparent; + border: none; + color: var(--vscode-descriptionForeground); + cursor: pointer; + padding: 4px 8px; + font-size: 14px; + opacity: 0.6; + transition: opacity 0.15s ease; + } + + .env-variable-remove:hover { + opacity: 1; + color: var(--vscode-errorForeground); + } + .yolo-mode-section { display: flex; align-items: center; @@ -1068,6 +1324,62 @@ const styles = ` opacity: 0.95; } + .plan-content { + font-size: 13px; + line-height: 1.6; + } + + .plan-content h1, .plan-content h2, .plan-content h3 { + margin: 12px 0 6px; + font-weight: 600; + } + + .plan-content h1 { font-size: 16px; } + .plan-content h2 { font-size: 14px; } + .plan-content h3 { font-size: 13px; } + + .plan-content ul, .plan-content ol { + padding-left: 20px; + margin: 4px 0; + } + + .plan-content code { + font-family: var(--vscode-editor-font-family); + font-size: 12px; + background: rgba(127, 127, 127, 0.15); + padding: 1px 4px; + border-radius: 3px; + } + + .plan-actions { + margin-top: 12px; + padding-top: 10px; + border-top: 1px solid var(--vscode-panel-border); + } + + .plan-actions-label { + font-size: 11px; + color: var(--vscode-descriptionForeground); + margin-bottom: 8px; + } + + .plan-action-btn { + display: inline-block; + background: var(--vscode-button-secondaryBackground, rgba(128, 128, 128, 0.2)); + color: var(--vscode-button-secondaryForeground, var(--vscode-foreground)); + border: 1px solid var(--vscode-panel-border); + padding: 5px 12px; + border-radius: 4px; + font-size: 12px; + cursor: pointer; + margin: 0 6px 6px 0; + } + + .plan-action-btn:hover { + background: var(--vscode-list-hoverBackground); + border-color: var(--vscode-focusBorder); + } + /* Diff display styles for Edit tool */ .diff-container { border: 1px solid var(--vscode-panel-border); @@ -1331,7 +1643,8 @@ const styles = ` } .input-container { - padding: 10px; + padding: 1px 10px 10px 10px; + margin: 0; border-top: 1px solid var(--vscode-panel-border); background-color: var(--vscode-panel-background); display: flex; @@ -1339,25 +1652,188 @@ const styles = ` position: relative; } - .input-modes { + .model-selector-row { display: flex; - gap: 16px; align-items: center; - padding-bottom: 5px; - font-size: 9.5px; + gap: 6px; + margin-top: 6px; + margin-bottom: 6px; + overflow: hidden; } - .mode-toggle { + .model-selector-new { + font-size: 9px; + font-weight: 700; + color: #fff; + background: linear-gradient(135deg, #f97316, #ea580c); + padding: 2px 6px; + border-radius: 4px; + letter-spacing: 0.5px; + } + + .model-selector-main { display: flex; align-items: center; gap: 6px; + padding: 4px 10px; + background: rgba(139, 92, 246, 0.1); + border: 1px solid rgba(139, 92, 246, 0.3); + border-radius: 20px; color: var(--vscode-foreground); - opacity: 0.8; - transition: opacity 0.2s ease; - } - - .mode-toggle span { - cursor: pointer; + font-size: 10px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s ease; + white-space: nowrap; + } + + .model-selector-main:hover { + background: rgba(139, 92, 246, 0.18); + border-color: rgba(139, 92, 246, 0.4); + } + + #modelDropdownBtn { + background: none; + border-color: var(--vscode-panel-border); + } + + #modelDropdownBtn:hover { + background: rgba(128, 128, 128, 0.15); + border-color: var(--vscode-focusBorder); + } + + #modelDropdownBtn svg { + color: var(--vscode-descriptionForeground); + width: 8px; + height: 8px; + } + + .model-selector-main svg { + color: #8b5cf6; + width: 12px; + height: 12px; + } + + .model-quick-select { + display: flex; + align-items: center; + gap: 4px; + overflow-x: auto; + overflow-y: hidden; + flex: 1; + min-width: 0; + scrollbar-width: none; + -ms-overflow-style: none; + } + + .model-quick-select::-webkit-scrollbar { + display: none; + } + + .model-quick-btn { + display: flex; + align-items: center; + gap: 3px; + padding: 4px 8px; + background: transparent; + border: 1px solid rgba(139, 92, 246, 0.2); + border-radius: 20px; + color: var(--vscode-foreground); + font-size: 10px; + cursor: pointer; + transition: all 0.15s ease; + opacity: 0.8; + white-space: nowrap; + } + + .model-quick-btn:hover { + background: rgba(139, 92, 246, 0.1); + border-color: rgba(139, 92, 246, 0.3); + opacity: 1; + } + + .model-quick-btn.selected { + background: rgba(139, 92, 246, 0.18); + border-color: rgba(139, 92, 246, 0.4); + opacity: 1; + } + + .model-quick-icon { + font-size: 10px; + } + + .model-quick-select { + mask-image: linear-gradient(to right, black calc(100% - 20px), transparent 100%); + -webkit-mask-image: linear-gradient(to right, black calc(100% - 20px), transparent 100%); + } + + .model-more-btn { + display: flex; + align-items: center; + gap: 2px; + padding: 4px 8px; + background: transparent; + border: 1px solid rgba(139, 92, 246, 0.2); + border-radius: 20px; + color: var(--vscode-foreground); + font-size: 10px; + cursor: pointer; + transition: all 0.15s ease; + white-space: nowrap; + opacity: 0.7; + flex-shrink: 0; + } + + .model-more-btn:hover { + background: rgba(139, 92, 246, 0.1); + border-color: rgba(139, 92, 246, 0.3); + opacity: 1; + } + + .model-more-btn.model-dropdown-btn { + padding: 4px 10px; + font-size: 11px; + border-color: var(--vscode-panel-border); + } + + .model-more-btn.model-dropdown-btn:hover { + background: var(--vscode-list-hoverBackground); + border-color: var(--vscode-focusBorder); + } + + .model-more-btn svg { + width: 10px; + height: 10px; + } + + .input-modes { + display: flex; + gap: 16px; + align-items: center; + padding-bottom: 5px; + font-size: 9.5px; + } + + .mode-toggle { + display: flex; + align-items: center; + gap: 5px; + color: var(--vscode-foreground); + opacity: 0.7; + transition: opacity 0.2s ease; + font-size: 10px; + } + + .left-controls .mode-toggle { + padding: 3px 0; + } + + .left-controls .mode-toggle span { + cursor: pointer; + } + + .mode-toggle span { + cursor: pointer; transition: opacity 0.2s ease; } @@ -1411,9 +1887,54 @@ const styles = ` background-color: var(--vscode-input-background); border: 1px solid var(--vscode-input-border); border-radius: 6px; + overflow: visible; + } + + .image-preview-container { + display: flex; + flex-wrap: wrap; + gap: 6px; + padding: 8px 8px 0; + } + + .image-preview-item { + position: relative; + width: 56px; + height: 56px; + border-radius: 6px; overflow: hidden; + border: 1px solid var(--vscode-input-border); + } + + .image-preview-item img { + width: 100%; + height: 100%; + object-fit: cover; + } + + .image-preview-remove { + position: absolute; + top: 2px; + right: 2px; + width: 16px; + height: 16px; + border-radius: 50%; + border: none; + background: rgba(0, 0, 0, 0.6); + color: #fff; + font-size: 10px; + line-height: 16px; + text-align: center; + cursor: pointer; + padding: 0; + display: none; + } + + .image-preview-item:hover .image-preview-remove { + display: block; } + .textarea-wrapper:focus-within { border-color: var(--vscode-focusBorder); } @@ -1431,6 +1952,7 @@ const styles = ` line-height: 1.4; overflow-y: hidden; resize: none; + border-radius: 6px 6px 0 0; } .input-field:focus { @@ -1452,6 +1974,7 @@ const styles = ` padding: 2px 4px; border-top: 1px solid var(--vscode-panel-border); background-color: var(--vscode-input-background); + border-radius: 0 0 6px 6px; } .left-controls { @@ -1461,24 +1984,51 @@ const styles = ` } .model-selector { - background-color: rgba(128, 128, 128, 0.15); + background: linear-gradient(135deg, rgba(139, 92, 246, 0.15), rgba(16, 185, 129, 0.15)); color: var(--vscode-foreground); - border: none; - padding: 3px 7px; - border-radius: 4px; + border: 1px solid rgba(139, 92, 246, 0.3); + padding: 4px 10px; + border-radius: 6px; cursor: pointer; font-size: 11px; font-weight: 500; transition: all 0.2s ease; - opacity: 0.9; display: flex; align-items: center; - gap: 4px; + gap: 6px; } .model-selector:hover { - background-color: rgba(128, 128, 128, 0.25); - opacity: 1; + background: linear-gradient(135deg, rgba(139, 92, 246, 0.25), rgba(16, 185, 129, 0.25)); + border-color: rgba(139, 92, 246, 0.5); + } + + .model-selector-label { + display: flex; + align-items: center; + gap: 6px; + } + + .model-selector-label #selectedModel { + font-weight: 600; + color: #a78bfa; + } + + .model-selector-examples { + font-size: 10px; + opacity: 0.6; + font-weight: 400; + } + + .model-selector-badge { + font-size: 8px; + font-weight: 700; + padding: 2px 5px; + border-radius: 3px; + background: linear-gradient(135deg, #f59e0b, #ea580c); + color: white; + text-transform: uppercase; + letter-spacing: 0.3px; } .tools-btn { @@ -1502,69 +2052,194 @@ const styles = ` opacity: 1; } - .slash-btn, - .at-btn { - background-color: transparent; - color: var(--vscode-foreground); + .plus-btn { + background: none; border: none; - padding: 4px 6px; - border-radius: 4px; + color: var(--vscode-foreground); + font-size: 16px; + line-height: 1; cursor: pointer; - font-size: 13px; - font-weight: 600; + padding: 2px 6px; + border-radius: 4px; + opacity: 0.6; transition: all 0.2s ease; } - .slash-btn:hover, - .at-btn:hover { - background-color: var(--vscode-list-hoverBackground); + .plus-btn:hover { + opacity: 1; + background-color: rgba(128, 128, 128, 0.2); } - .image-btn { - background-color: transparent; - color: var(--vscode-foreground); - border: none; - padding: 4px; - border-radius: 4px; - cursor: pointer; + .input-dropdown-btn { display: flex; align-items: center; - justify-content: center; - width: 24px; - height: 24px; - transition: all 0.2s ease; - padding-top: 6px; + gap: 3px; + background: none; + border: none; + color: var(--vscode-descriptionForeground); + font-size: 12px; + cursor: pointer; + padding: 2px 6px; + border-radius: 4px; + transition: all 0.15s ease; } - .image-btn:hover { - background-color: var(--vscode-list-hoverBackground); + .input-dropdown-btn:hover { + color: var(--vscode-foreground); + background-color: rgba(128, 128, 128, 0.15); } - .send-btn { - background-color: var(--vscode-button-background); - color: var(--vscode-button-foreground); - border: none; - padding: 3px 7px; - border-radius: 4px; - cursor: pointer; - font-size: 11px; - font-weight: 500; - transition: all 0.2s ease; + #connectBtn { + color: var(--vscode-foreground); + background-color: rgba(128, 128, 128, 0.12); + padding: 3px 8px; } - .send-btn div { + #connectBtn:hover { + background-color: rgba(128, 128, 128, 0.25); + } + + .input-dropdown-btn svg { + opacity: 0.6; + } + + + .input-toggle-btn { display: flex; align-items: center; - justify-content: center; - gap: 2px; + background: none; + border: 1px solid transparent; + color: var(--vscode-descriptionForeground); + font-size: 12px; + cursor: pointer; + padding: 1px 5px; + border-radius: 4px; + transition: all 0.15s ease; } - .send-btn span { - line-height: 1; + .input-toggle-btn:hover { + color: var(--vscode-foreground); + background-color: rgba(128, 128, 128, 0.15); } - .send-btn:hover { - background-color: var(--vscode-button-hoverBackground); + .input-toggle-btn.active { + color: var(--vscode-button-background); + background-color: rgba(0, 122, 204, 0.12); + border-color: rgba(0, 122, 204, 0.3); + } + + .connect-dropdown-wrapper { + position: relative; + } + + .connect-menu { + position: absolute; + bottom: 100%; + left: 0; + margin-bottom: 6px; + background-color: var(--vscode-menu-background); + border: 1px solid var(--vscode-menu-border); + border-radius: 8px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); + min-width: 180px; + padding: 6px 0; + z-index: 1000; + } + + .connect-menu-header { + padding: 8px 14px 6px; + font-size: 11px; + font-weight: 600; + color: var(--vscode-descriptionForeground); + } + + .connect-menu-item { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 8px 14px; + background: none; + border: none; + color: var(--vscode-foreground); + font-size: 13px; + cursor: pointer; + text-align: left; + transition: background-color 0.1s ease; + } + + .connect-menu-item:hover { + background-color: var(--vscode-list-hoverBackground); + } + + .connect-menu-item svg { + color: var(--vscode-descriptionForeground); + flex-shrink: 0; + } + + .slash-btn, + .at-btn { + background-color: transparent; + color: var(--vscode-foreground); + border: none; + padding: 4px 6px; + border-radius: 4px; + cursor: pointer; + font-size: 13px; + font-weight: 600; + transition: all 0.2s ease; + } + + .slash-btn:hover, + .at-btn:hover { + background-color: var(--vscode-list-hoverBackground); + } + + .image-btn { + background-color: transparent; + color: var(--vscode-foreground); + border: none; + padding: 4px; + border-radius: 4px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + transition: all 0.2s ease; + padding-top: 6px; + } + + .image-btn:hover { + background-color: var(--vscode-list-hoverBackground); + } + + .send-btn { + background-color: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: none; + padding: 3px 7px; + border-radius: 4px; + cursor: pointer; + font-size: 11px; + font-weight: 500; + transition: all 0.2s ease; + } + + .send-btn div { + display: flex; + align-items: center; + justify-content: center; + gap: 2px; + } + + .send-btn span { + line-height: 1; + } + + .send-btn:hover { + background-color: var(--vscode-button-hoverBackground); } .send-btn:disabled { @@ -1572,6 +2247,28 @@ const styles = ` cursor: not-allowed; } + .stop-inline-btn { + background-color: #ef4444; + color: #fff; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 11px; + font-weight: 500; + display: none; + align-items: center; + justify-content: center; + gap: 2px; + min-width: 39px; + min-height: 11px; + padding: 3px 7px; + box-sizing: content-box; + } + + .stop-inline-btn:hover { + background-color: #dc2626; + } + .secondary-button { background-color: var(--vscode-button-secondaryBackground, rgba(128, 128, 128, 0.2)); color: var(--vscode-button-secondaryForeground, var(--vscode-foreground)); @@ -1765,7 +2462,7 @@ const styles = ` left: 0; width: 100%; height: 100%; - background-color: rgba(0, 0, 0, 0.5); + background-color: rgba(0, 0, 0, 0.75); z-index: 1000; display: flex; align-items: center; @@ -1778,7 +2475,7 @@ const styles = ` border-radius: 8px; width: 700px; max-width: 90vw; - max-height: 80vh; + max-height: 90vh; display: flex; flex-direction: column; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); @@ -1792,6 +2489,7 @@ const styles = ` justify-content: space-between; align-items: center; flex-shrink: 0; + background: linear-gradient(135deg, rgba(139, 92, 246, 0.08), rgba(59, 130, 246, 0.08)); } .tools-modal-body { @@ -1830,13 +2528,17 @@ const styles = ` } /* MCP Modal content area improvements */ - #mcpModal * { + #mcpModal *, + #skillsModal *, + #pluginsModal * { box-sizing: border-box; } - #mcpModal .tools-list { + #mcpModal .tools-list, + #skillsModal .tools-list, + #pluginsModal .tools-list { padding: 24px; - max-height: calc(80vh - 120px); + max-height: calc(90vh - 120px); overflow-y: auto; width: 100%; } @@ -1930,1047 +2632,1941 @@ const styles = ` align-self: flex-start; } - /* Thinking intensity slider */ - .thinking-slider-container { - position: relative; - padding: 0px 16px; - margin: 12px 0; + /* Model modal styles */ + .model-modal-content { + width: 520px; + max-width: 90vw; + max-height: 80vh; + overflow-y: auto; + display: flex; + flex-direction: column; } - .thinking-slider { - width: 100%; - height: 4px; - -webkit-appearance: none; - appearance: none; - background: var(--vscode-panel-border); - outline: none !important; - border: none; - cursor: pointer; - border-radius: 2px; + .model-section { + padding: 16px; } - .thinking-slider::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 16px; - height: 16px; - background: var(--vscode-foreground); - cursor: pointer; - border-radius: 50%; - transition: transform 0.2s ease; + .model-section.opencredits-section { + border-top: 1px solid var(--vscode-panel-border); } - .thinking-slider::-webkit-slider-thumb:hover { - transform: scale(1.2); + .model-section-header { + display: flex; + flex-direction: column; + gap: 4px; + margin-bottom: 14px; } - .thinking-slider::-moz-range-thumb { - width: 16px; - height: 16px; - background: var(--vscode-foreground); - cursor: pointer; - border-radius: 50%; - border: none; - transition: transform 0.2s ease; + .model-section-title { + font-size: 11px; + font-weight: 700; + letter-spacing: 1px; + display: flex; + align-items: center; + gap: 8px; + color: white; } - .thinking-slider::-moz-range-thumb:hover { - transform: scale(1.2); + .new-badge { + font-size: 9px; + font-weight: 700; + padding: 3px 8px; + border-radius: 4px; + background: linear-gradient(135deg, #f59e0b, #ea580c); + color: white; + text-transform: uppercase; + letter-spacing: 0.5px; + box-shadow: 0 2px 8px rgba(245, 158, 11, 0.4); } - .slider-labels { - display: flex; - justify-content: space-between; - margin-top: 12px; - padding: 0 8px; + .beta-badge { + font-size: 9px; + font-weight: 700; + padding: 3px 8px; + border-radius: 4px; + background: rgba(127, 127, 127, 0.25); + color: var(--vscode-descriptionForeground); + text-transform: uppercase; + letter-spacing: 0.5px; + margin-left: auto; + cursor: default; + position: relative; } - .slider-label { + .beta-badge:hover::after { + content: attr(data-tooltip); + position: absolute; + top: calc(100% + 6px); + right: 0; + background: var(--vscode-editorHoverWidget-background, #1e1e1e); + color: var(--vscode-editorHoverWidget-foreground, #ccc); + border: 1px solid var(--vscode-editorHoverWidget-border, #454545); + padding: 4px 8px; + border-radius: 4px; + font-size: 11px; + font-weight: 400; + letter-spacing: 0; + text-transform: none; + white-space: nowrap; + z-index: 100; + } + + .model-section-subtitle { font-size: 12px; color: var(--vscode-descriptionForeground); - opacity: 0.7; - transition: all 0.2s ease; - text-align: center; - width: 100px; - cursor: pointer; } - .slider-label:hover { - opacity: 1; - color: var(--vscode-foreground); + .model-section-divider { + height: 1px; + background: var(--vscode-panel-border); + margin: 0 16px; } - .slider-label.active { - opacity: 1; - color: var(--vscode-foreground); - font-weight: 500; + /* Flexible grid for model cards */ + .model-cards-container { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 12px; } - .slider-label:first-child { - margin-left: -50px; + @media (min-width: 600px) { + .model-cards-container { + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + } } - .slider-label:last-child { - margin-right: -50px; + .model-card { + position: relative; + padding: 12px; + background: var(--vscode-input-background); + border: 1px solid var(--vscode-panel-border); + border-left: 3px solid #10b981; + border-radius: 8px; + cursor: pointer; + transition: all 0.15s ease; + display: flex; + flex-direction: column; + gap: 4px; } - .settings-group { - padding-bottom: 20px; - margin-bottom: 40px; - border-bottom: 1px solid rgba(255, 255, 255, 0.05); + .model-card:hover { + border-color: #10b981; + border-left: 3px solid #10b981; + background: rgba(16, 185, 129, 0.1); } - .settings-group h3 { - margin: 0 0 12px 0; + .model-card.selected { + border-color: #10b981; + border-left: 3px solid #10b981; + background: rgba(16, 185, 129, 0.15); + } + + .model-card-name { font-size: 13px; font-weight: 600; color: var(--vscode-foreground); + line-height: 1.3; } + .model-card-provider { + font-size: 10px; + color: var(--vscode-descriptionForeground); + } - /* Thinking intensity modal */ - .thinking-modal-description { - padding: 0px 20px; - font-size: 12px; + .model-card-price { + font-size: 10px; color: var(--vscode-descriptionForeground); - line-height: 1.5; - text-align: center; - margin: 20px; - margin-bottom: 0px; + margin-top: 4px; } - .thinking-modal-actions { - padding-top: 20px; - text-align: right; - border-top: 1px solid var(--vscode-widget-border); + .model-card-requests { + font-size: 10px; + color: var(--vscode-descriptionForeground); + margin-top: 4px; } - .confirm-btn { - background-color: var(--vscode-button-background); - color: var(--vscode-button-foreground); - border: 1px solid var(--vscode-panel-border); - padding: 6px 12px; - border-radius: 4px; - cursor: pointer; - font-size: 12px; - font-weight: 400; - transition: all 0.2s ease; - display: inline-flex; - align-items: center; - gap: 5px; + .claude-card-requests { + font-size: 10px; + color: var(--vscode-descriptionForeground); + margin-top: 4px; } - .confirm-btn:hover { - background-color: var(--vscode-button-background); - border-color: var(--vscode-focusBorder); + .model-section-links { + display: flex; + justify-content: space-between; + width: 100%; + grid-column: 1 / -1; } - /* Slash commands modal */ - .slash-commands-search { - padding: 16px 20px; - border-bottom: 1px solid var(--vscode-panel-border); - position: sticky; - top: 0; - background-color: var(--vscode-editor-background); - z-index: 10; + .model-section-links a { + font-size: 11px; + color: var(--vscode-foreground); + text-decoration: none; } - .search-input-wrapper { - display: flex; - align-items: center; - border: 1px solid var(--vscode-input-border); - border-radius: 6px; - background-color: var(--vscode-input-background); - transition: all 0.2s ease; - position: relative; + .model-section-links a:hover { + text-decoration: underline; } - .search-input-wrapper:focus-within { - border-color: var(--vscode-focusBorder); - box-shadow: 0 0 0 1px var(--vscode-focusBorder); + .custom-provider-field { + margin-bottom: 12px; + overflow: visible; } - .search-prefix { - display: flex; - align-items: center; - justify-content: center; - min-width: 32px; - height: 32px; - background-color: var(--vscode-button-secondaryBackground); - color: var(--vscode-button-secondaryForeground); - font-size: 13px; + .custom-provider-field label { + display: block; + font-size: 11px; font-weight: 600; - border-radius: 4px 0 0 4px; - border-right: 1px solid var(--vscode-input-border); + color: var(--vscode-foreground); + margin-bottom: 4px; } - .slash-commands-search input { - flex: 1; - padding: 8px 12px; - border: none !important; - background: transparent; + .custom-provider-field input { + width: 100%; + padding: 8px 10px; + font-size: 12px; + font-family: inherit; color: var(--vscode-input-foreground); - font-size: 13px; - outline: none !important; - box-shadow: none !important; + background: var(--vscode-input-background); + border: 1px solid var(--vscode-input-border, rgba(255,255,255,0.1)); + border-radius: 4px; + outline: none; } - .slash-commands-search input:focus { - border: none !important; - outline: none !important; - box-shadow: none !important; + .custom-provider-field input:focus { + border-color: var(--vscode-focusBorder); } - .slash-commands-search input::placeholder { - color: var(--vscode-input-placeholderForeground); + .model-combo { + position: relative; + max-width: 100%; + overflow: visible; } - .command-input-wrapper { - display: flex; - align-items: center; - border: 1px solid var(--vscode-input-border); - border-radius: 6px; - background-color: var(--vscode-input-background); - transition: all 0.2s ease; + .model-combo-input { width: 100%; - position: relative; + padding: 8px 28px 8px 10px; + font-size: 12px; + font-family: inherit; + color: var(--vscode-input-foreground); + background: var(--vscode-input-background); + border: 1px solid var(--vscode-input-border, rgba(255,255,255,0.2)); + border-radius: 4px; + outline: none; + box-sizing: border-box; } - .command-input-wrapper:focus-within { + .model-combo::after { + content: ''; + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 5px solid var(--vscode-descriptionForeground, #888); + pointer-events: none; + } + + .model-combo-input:focus { border-color: var(--vscode-focusBorder); - box-shadow: 0 0 0 1px var(--vscode-focusBorder); } - .command-prefix { - display: flex; - align-items: center; - justify-content: center; - min-width: 32px; - height: 32px; - background-color: var(--vscode-button-secondaryBackground); - color: var(--vscode-button-secondaryForeground); - font-size: 12px; - font-weight: 600; - border-radius: 4px 0 0 4px; - border-right: 1px solid var(--vscode-input-border); + .model-combo-dropdown { + display: none; + position: absolute; + top: 100%; + left: 0; + right: 0; + max-height: 120px; + overflow-y: auto; + background: var(--vscode-dropdown-background, #1e1e1e); + border: 1px solid var(--vscode-dropdown-border, rgba(255,255,255,0.2)); + border-radius: 4px; + margin-top: 2px; + z-index: 100; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); } - .slash-commands-section { - margin-bottom: 32px; + .model-combo.open .model-combo-dropdown { + display: block; } - .slash-commands-section:last-child { - margin-bottom: 16px; + .model-combo-option { + padding: 6px 10px; + font-size: 12px; + cursor: pointer; + color: var(--vscode-dropdown-foreground); } - .slash-commands-section h3 { - margin: 16px 20px 12px 20px; - font-size: 14px; - font-weight: 600; - color: var(--vscode-foreground); + .model-combo-option:hover { + background: var(--vscode-list-hoverBackground, rgba(255,255,255,0.05)); } - .slash-commands-info { - padding: 12px 20px; - background-color: rgba(255, 149, 0, 0.1); - border: 1px solid rgba(255, 149, 0, 0.2); - border-radius: 4px; - margin: 0 20px 16px 20px; + .model-combo-option .model-combo-option-name { + font-weight: 500; } - .slash-commands-info p { - margin: 0; - font-size: 11px; - color: var(--vscode-descriptionForeground); - text-align: center; - opacity: 0.9; + .model-combo-option .model-combo-option-id { + font-size: 10px; + opacity: 0.6; } - .prompt-snippet-item { - border-left: 2px solid var(--vscode-charts-blue); - background-color: rgba(0, 122, 204, 0.03); + .model-combo-custom { + padding: 6px 10px; + font-size: 12px; + cursor: pointer; + color: var(--vscode-textLink-foreground, #3794ff); + border-top: 1px solid var(--vscode-dropdown-border, rgba(255,255,255,0.1)); } - .prompt-snippet-item:hover { - background-color: rgba(0, 122, 204, 0.08); + .model-combo-custom:hover { + background: var(--vscode-list-hoverBackground, rgba(255,255,255,0.05)); } - .add-snippet-item { - border-left: 2px solid var(--vscode-charts-green); - background-color: rgba(0, 200, 83, 0.03); - border-style: dashed; + .model-comparison-header { + font-size: 11px; + color: var(--vscode-descriptionForeground); + margin-bottom: 10px; + line-height: 1.5; } - .add-snippet-item:hover { - background-color: rgba(0, 200, 83, 0.08); - border-style: solid; + .model-card-unlock { + font-size: 9px; + color: #10b981; + margin-top: 6px; + font-weight: 500; } - .add-snippet-form { - background-color: var(--vscode-editor-background); - border: 1px solid var(--vscode-panel-border); - border-radius: 6px; - padding: 16px; - margin: 8px 0; - animation: slideDown 0.2s ease; + .model-card.pending { + border-color: rgba(249, 115, 22, 0.5); + background: rgba(249, 115, 22, 0.1); } - .add-snippet-form .form-group { - margin-bottom: 12px; + .model-card-price-label { + display: none; } - .add-snippet-form label { - display: block; - margin-bottom: 4px; + .price-current { font-weight: 500; - font-size: 12px; - color: var(--vscode-foreground); } - .add-snippet-form textarea { - width: 100%; - padding: 6px 8px; - border: 1px solid var(--vscode-input-border); + .price-comparison { + margin-left: 4px; + opacity: 0.7; + } + + .price-comparison s { + text-decoration: line-through; + } + + /* Savings badge */ + .savings-badge { + position: absolute; + top: 8px; + right: 8px; + font-size: 9px; + font-weight: 600; + padding: 2px 6px; border-radius: 3px; - background-color: var(--vscode-input-background); - color: var(--vscode-input-foreground); - font-size: 12px; - font-family: var(--vscode-font-family); - box-sizing: border-box; + background: rgba(16, 185, 129, 0.15); + color: #10b981; + border: 1px solid rgba(16, 185, 129, 0.3); } - .add-snippet-form .command-input-wrapper input { - flex: 1; - padding: 6px 8px; - border: none !important; - background: transparent; - color: var(--vscode-input-foreground); - font-size: 12px; - font-family: var(--vscode-font-family); - outline: none !important; - box-shadow: none !important; + /* More models card */ + .more-models-card { + background: var(--vscode-button-secondaryBackground) !important; + border-style: dashed !important; } - .add-snippet-form .command-input-wrapper input:focus { - border: none !important; - outline: none !important; - box-shadow: none !important; + .more-models-card:hover { + background: var(--vscode-button-secondaryHoverBackground) !important; } - .add-snippet-form textarea:focus { - outline: none; - border-color: var(--vscode-focusBorder); + .more-models-card .savings-badge { + display: none; } - .add-snippet-form input::placeholder, - .add-snippet-form textarea::placeholder { - color: var(--vscode-input-placeholderForeground); + /* All models browser */ + .all-models-search { + padding: 12px 16px; + border-bottom: 1px solid var(--vscode-panel-border); } - .add-snippet-form textarea { - resize: vertical; - min-height: 60px; + .all-models-search input { + width: 100%; + padding: 8px 12px; + border: 1px solid var(--vscode-input-border); + background: var(--vscode-input-background); + color: var(--vscode-input-foreground); + border-radius: 4px; + font-size: 13px; + box-sizing: border-box; } - .add-snippet-form .form-buttons { - display: flex; - gap: 8px; - justify-content: flex-end; - margin-top: 12px; + .all-models-search input:focus { + outline: none; + border-color: var(--vscode-focusBorder); } - .custom-snippet-item { - position: relative; + .all-models-list { + max-height: 400px; + overflow-y: auto; + padding: 4px 8px; } - .snippet-actions { + .all-models-item { display: flex; + justify-content: space-between; align-items: center; - opacity: 0; - transition: opacity 0.2s ease; - margin-left: 8px; + padding: 10px 16px; + border-radius: 6px; + cursor: pointer; + margin-bottom: 2px; + background: var(--vscode-list-hoverBackground); } - .custom-snippet-item:hover .snippet-actions { - opacity: 1; + .all-models-item:hover { + background: var(--vscode-list-activeSelectionBackground); } - .snippet-delete-btn { - background: none; - border: none; - color: var(--vscode-descriptionForeground); - cursor: pointer; - padding: 4px; - border-radius: 3px; - font-size: 12px; - transition: all 0.2s ease; - opacity: 0.7; + .all-models-item.selected { + background: var(--vscode-list-activeSelectionBackground); + border: 1px solid var(--vscode-focusBorder); } - .snippet-delete-btn:hover { - background-color: rgba(231, 76, 60, 0.1); - color: var(--vscode-errorForeground); - opacity: 1; + .all-models-item-main { + flex: 1; + min-width: 0; } - .slash-commands-list { - display: grid; - gap: 6px; - padding: 0 20px; + .all-models-item-name { + font-size: 13px; + font-weight: 500; + color: var(--vscode-foreground); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } - .slash-command-item { + .all-models-item-provider { + font-size: 11px; + color: var(--vscode-descriptionForeground); + margin-top: 2px; + } + + .all-models-item-details { display: flex; - align-items: center; gap: 12px; - padding: 10px 14px; - border-radius: 4px; - cursor: pointer; - transition: all 0.15s ease; - border: 1px solid transparent; - background-color: transparent; + align-items: center; + flex-shrink: 0; } - .slash-command-item:hover { - background-color: var(--vscode-list-hoverBackground); - border-color: var(--vscode-list-hoverBackground); + .all-models-item-context { + font-size: 11px; + color: var(--vscode-descriptionForeground); + background: var(--vscode-badge-background); + padding: 2px 6px; + border-radius: 3px; } - .slash-command-icon { - font-size: 16px; - min-width: 20px; + .all-models-item-price { + font-size: 11px; + color: var(--vscode-descriptionForeground); + } + + .all-models-loading, + .all-models-error, + .all-models-empty { text-align: center; - opacity: 0.8; + padding: 40px 20px; + color: var(--vscode-descriptionForeground); } - .slash-command-content { - flex: 1; + .all-models-error { + color: var(--vscode-errorForeground); } - .slash-command-title { - font-size: 13px; - font-weight: 500; - color: var(--vscode-foreground); - margin-bottom: 2px; + + /* Claude Code model cards */ + .claude-cards-container { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 10px; } - .slash-command-description { - font-size: 11px; - color: var(--vscode-descriptionForeground); - opacity: 0.7; - line-height: 1.3; + .claude-card { + padding: 12px; + background: var(--vscode-input-background); + border: 1px solid var(--vscode-panel-border); + border-left: 3px solid #8b5cf6; + border-radius: 8px; + cursor: pointer; + transition: all 0.15s ease; + display: flex; + flex-direction: column; + gap: 4px; } - /* Quick command input */ - .custom-command-item { - cursor: default; + .claude-card:hover { + border-color: #8b5cf6; + border-left: 3px solid #8b5cf6; + background: rgba(139, 92, 246, 0.1); } - .custom-command-item .command-input-wrapper { - margin-top: 4px; - max-width: 200px; + .claude-card.selected { + border-color: #8b5cf6; + border-left: 3px solid #8b5cf6; + background: rgba(139, 92, 246, 0.15); } - .custom-command-item .command-input-wrapper input { - flex: 1; - padding: 4px 6px; - border: none !important; - background: transparent; - color: var(--vscode-input-foreground); - font-size: 11px; - font-family: var(--vscode-editor-font-family); - outline: none !important; - box-shadow: none !important; + .claude-card-name { + font-size: 13px; + font-weight: 600; + color: var(--vscode-foreground); } - .custom-command-item .command-input-wrapper input:focus { - border: none !important; - outline: none !important; - box-shadow: none !important; + .claude-card-desc { + font-size: 10px; + color: var(--vscode-descriptionForeground); + line-height: 1.3; } - .custom-command-item .command-input-wrapper input::placeholder { - color: var(--vscode-input-placeholderForeground); - opacity: 0.7; + /* Thinking intensity slider */ + .thinking-slider-container { + position: relative; + padding: 0px 16px; + margin: 12px 0; } - .status { - padding: 8px 12px; - background: linear-gradient(135deg, #1e1e1e 0%, #2d2d2d 100%); - color: #e1e1e1; - font-size: 12px; - border-top: 1px solid var(--vscode-panel-border); - display: flex; - align-items: center; - gap: 8px; - font-weight: 500; + .thinking-slider { + width: 100%; + height: 4px; + -webkit-appearance: none; + appearance: none; + background: var(--vscode-panel-border); + outline: none !important; + border: none; + cursor: pointer; + border-radius: 2px; } - .status-indicator { - width: 8px; - height: 8px; + .thinking-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 16px; + height: 16px; + background: var(--vscode-foreground); + cursor: pointer; border-radius: 50%; - flex-shrink: 0; + transition: transform 0.2s ease; } - .status.ready .status-indicator { - background-color: #00d26a; - box-shadow: 0 0 6px rgba(0, 210, 106, 0.5); + .thinking-slider::-webkit-slider-thumb:hover { + transform: scale(1.2); } - .status.processing .status-indicator { - background-color: #ff9500; - box-shadow: 0 0 6px rgba(255, 149, 0, 0.5); - animation: pulse 1.5s ease-in-out infinite; + .thinking-slider::-moz-range-thumb { + width: 16px; + height: 16px; + background: var(--vscode-foreground); + cursor: pointer; + border-radius: 50%; + border: none; + transition: transform 0.2s ease; } - .status.error .status-indicator { - background-color: #ff453a; - box-shadow: 0 0 6px rgba(255, 69, 58, 0.5); + .thinking-slider::-moz-range-thumb:hover { + transform: scale(1.2); } - @keyframes pulse { - 0%, 100% { opacity: 1; transform: scale(1); } - 50% { opacity: 0.7; transform: scale(1.1); } + .slider-labels { + display: flex; + justify-content: space-between; + margin-top: 12px; + padding: 0 8px; } - .status-text { - flex: 1; + .slider-label { + font-size: 12px; + color: var(--vscode-descriptionForeground); + opacity: 0.7; + transition: all 0.2s ease; + text-align: center; + width: 100px; + cursor: pointer; } - .status-text .usage-badge { - display: inline-flex; - align-items: center; - gap: 4px; - color: inherit; - text-decoration: none; - background: rgba(255, 255, 255, 0.08); - padding: 2px 8px 2px 8px; - border-radius: 10px; - cursor: pointer; - transition: background 0.15s, transform 0.1s; + .slider-label:hover { + opacity: 1; + color: var(--vscode-foreground); } - .status-text .usage-badge:hover { - background: rgba(255, 255, 255, 0.15); - transform: translateY(-1px); + .slider-label.active { + opacity: 1; + color: var(--vscode-foreground); + font-weight: 500; } - .status-text .usage-badge:active { - transform: translateY(0); + .slider-label:first-child { + margin-left: -50px; } - .status-text .usage-icon { - width: 12px; - height: 12px; - flex-shrink: 0; + .slider-label:last-child { + margin-right: -50px; } - pre { - white-space: pre-wrap; - word-wrap: break-word; - margin: 0; + .settings-group { + padding-bottom: 20px; + margin-bottom: 40px; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); } - .session-badge { - margin-left: 16px; - background-color: var(--vscode-badge-background); - color: var(--vscode-badge-foreground); - padding: 4px 8px; - border-radius: 12px; - font-size: 11px; - font-weight: 500; - display: flex; - align-items: center; - gap: 4px; - transition: background-color 0.2s, transform 0.1s; + .settings-group h3 { + margin: 0 0 12px 0; + font-size: 13px; + font-weight: 600; + color: var(--vscode-foreground); } - .session-badge:hover { - background-color: var(--vscode-button-hoverBackground); - transform: scale(1.02); + + /* Thinking intensity modal */ + .thinking-modal-description { + padding: 0px 20px; + font-size: 12px; + color: var(--vscode-descriptionForeground); + line-height: 1.5; + text-align: center; + margin: 20px; + margin-bottom: 0px; + } + + .thinking-modal-actions { + padding-top: 20px; + text-align: right; + border-top: 1px solid var(--vscode-widget-border); + } + + .confirm-btn { + background-color: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: 1px solid var(--vscode-panel-border); + padding: 6px 12px; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + font-weight: 400; + transition: all 0.2s ease; + display: inline-flex; + align-items: center; + gap: 5px; + } + + .confirm-btn:hover { + background-color: var(--vscode-button-background); + border-color: var(--vscode-focusBorder); + } + + /* Slash commands modal */ + .slash-commands-search { + padding: 16px 20px; + border-bottom: 1px solid var(--vscode-panel-border); + position: sticky; + top: 0; + background-color: var(--vscode-editor-background); + z-index: 10; + } + + .search-input-wrapper { + display: flex; + align-items: center; + border: 1px solid var(--vscode-input-border); + border-radius: 6px; + background-color: var(--vscode-input-background); + transition: all 0.2s ease; + position: relative; + } + + .search-input-wrapper:focus-within { + border-color: var(--vscode-focusBorder); + box-shadow: 0 0 0 1px var(--vscode-focusBorder); + } + + .search-prefix { + display: flex; + align-items: center; + justify-content: center; + min-width: 32px; + height: 32px; + background-color: var(--vscode-button-secondaryBackground); + color: var(--vscode-button-secondaryForeground); + font-size: 13px; + font-weight: 600; + border-radius: 4px 0 0 4px; + border-right: 1px solid var(--vscode-input-border); + } + + .slash-commands-search input { + flex: 1; + padding: 8px 12px; + border: none !important; + background: transparent; + color: var(--vscode-input-foreground); + font-size: 13px; + outline: none !important; + box-shadow: none !important; + } + + .slash-commands-search input:focus { + border: none !important; + outline: none !important; + box-shadow: none !important; + } + + .slash-commands-search input::placeholder { + color: var(--vscode-input-placeholderForeground); + } + + .command-input-wrapper { + display: flex; + align-items: center; + border: 1px solid var(--vscode-input-border); + border-radius: 6px; + background-color: var(--vscode-input-background); + transition: all 0.2s ease; + width: 100%; + position: relative; + } + + .command-input-wrapper:focus-within { + border-color: var(--vscode-focusBorder); + box-shadow: 0 0 0 1px var(--vscode-focusBorder); + } + + .command-prefix { + display: flex; + align-items: center; + justify-content: center; + min-width: 32px; + height: 32px; + background-color: var(--vscode-button-secondaryBackground); + color: var(--vscode-button-secondaryForeground); + font-size: 12px; + font-weight: 600; + border-radius: 4px 0 0 4px; + border-right: 1px solid var(--vscode-input-border); + } + + .slash-commands-section { + margin-bottom: 32px; + } + + .slash-commands-section:last-child { + margin-bottom: 16px; + } + + .slash-commands-section h3 { + margin: 16px 20px 12px 20px; + font-size: 14px; + font-weight: 600; + color: var(--vscode-foreground); + } + + .slash-commands-info { + padding: 12px 20px; + background-color: rgba(255, 149, 0, 0.1); + border: 1px solid rgba(255, 149, 0, 0.2); + border-radius: 4px; + margin: 0 20px 16px 20px; + } + + .slash-commands-info p { + margin: 0; + font-size: 11px; + color: var(--vscode-descriptionForeground); + text-align: center; + opacity: 0.9; + } + + .prompt-snippet-item { + border-left: 2px solid var(--vscode-charts-blue); + background-color: rgba(0, 122, 204, 0.03); + } + + .prompt-snippet-item:hover { + background-color: rgba(0, 122, 204, 0.08); + } + + .add-snippet-item { + border-left: 2px solid var(--vscode-charts-green); + background-color: rgba(0, 200, 83, 0.03); + border-style: dashed; + } + + .add-snippet-item:hover { + background-color: rgba(0, 200, 83, 0.08); + border-style: solid; + } + + .add-snippet-form { + background-color: var(--vscode-editor-background); + border: 1px solid var(--vscode-panel-border); + border-radius: 6px; + padding: 16px; + margin: 8px 0; + animation: slideDown 0.2s ease; + } + + .add-snippet-form .form-group { + margin-bottom: 12px; + } + + .add-snippet-form label { + display: block; + margin-bottom: 4px; + font-weight: 500; + font-size: 12px; + color: var(--vscode-foreground); + } + + .add-snippet-form textarea { + width: 100%; + padding: 6px 8px; + border: 1px solid var(--vscode-input-border); + border-radius: 3px; + background-color: var(--vscode-input-background); + color: var(--vscode-input-foreground); + font-size: 12px; + font-family: var(--vscode-font-family); + box-sizing: border-box; + } + + .add-snippet-form .command-input-wrapper input { + flex: 1; + padding: 6px 8px; + border: none !important; + background: transparent; + color: var(--vscode-input-foreground); + font-size: 12px; + font-family: var(--vscode-font-family); + outline: none !important; + box-shadow: none !important; + } + + .add-snippet-form .command-input-wrapper input:focus { + border: none !important; + outline: none !important; + box-shadow: none !important; + } + + .add-snippet-form textarea:focus { + outline: none; + border-color: var(--vscode-focusBorder); + } + + .add-snippet-form input::placeholder, + .add-snippet-form textarea::placeholder { + color: var(--vscode-input-placeholderForeground); + } + + .add-snippet-form textarea { + resize: vertical; + min-height: 60px; + } + + .add-snippet-form .form-buttons { + display: flex; + gap: 8px; + justify-content: flex-end; + margin-top: 12px; + } + + .custom-snippet-item { + position: relative; + } + + .snippet-actions { + display: flex; + align-items: center; + opacity: 0; + transition: opacity 0.2s ease; + margin-left: 8px; + } + + .custom-snippet-item:hover .snippet-actions { + opacity: 1; + } + + .snippet-delete-btn { + background: none; + border: none; + color: var(--vscode-descriptionForeground); + cursor: pointer; + padding: 4px; + border-radius: 3px; + font-size: 12px; + transition: all 0.2s ease; + opacity: 0.7; + } + + .snippet-delete-btn:hover { + background-color: rgba(231, 76, 60, 0.1); + color: var(--vscode-errorForeground); + opacity: 1; + } + + .slash-commands-list { + display: grid; + gap: 6px; + padding: 0 20px; + } + + .slash-command-item { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 14px; + border-radius: 4px; + cursor: pointer; + transition: all 0.15s ease; + border: 1px solid transparent; + background-color: transparent; + } + + .slash-command-item:hover { + background-color: var(--vscode-list-hoverBackground); + border-color: var(--vscode-list-hoverBackground); + } + + .slash-command-icon { + font-size: 16px; + min-width: 20px; + text-align: center; + opacity: 0.8; + } + + .slash-command-content { + flex: 1; + } + + .slash-command-title { + font-size: 13px; + font-weight: 500; + color: var(--vscode-foreground); + margin-bottom: 2px; + } + + .slash-command-description { + font-size: 11px; + color: var(--vscode-descriptionForeground); + opacity: 0.7; + line-height: 1.3; + } + + /* Quick command input */ + .custom-command-item { + cursor: default; + } + + .custom-command-item .command-input-wrapper { + margin-top: 4px; + max-width: 200px; + } + + .custom-command-item .command-input-wrapper input { + flex: 1; + padding: 4px 6px; + border: none !important; + background: transparent; + color: var(--vscode-input-foreground); + font-size: 11px; + font-family: var(--vscode-editor-font-family); + outline: none !important; + box-shadow: none !important; + } + + .custom-command-item .command-input-wrapper input:focus { + border: none !important; + outline: none !important; + box-shadow: none !important; + } + + .custom-command-item .command-input-wrapper input::placeholder { + color: var(--vscode-input-placeholderForeground); + opacity: 0.7; + } + + .status { + padding: 8px 12px; + background: linear-gradient(135deg, #1e1e1e 0%, #2d2d2d 100%); + color: #e1e1e1; + font-size: 12px; + border-top: 1px solid var(--vscode-panel-border); + display: flex; + align-items: center; + gap: 8px; + font-weight: 500; + } + + .status-indicator { + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; + } + + .status.ready .status-indicator { + background-color: #00d26a; + box-shadow: 0 0 6px rgba(0, 210, 106, 0.5); + } + + .status.processing .status-indicator { + background-color: #ff9500; + box-shadow: 0 0 6px rgba(255, 149, 0, 0.5); + animation: pulse 1.5s ease-in-out infinite; + } + + .status.error .status-indicator { + background-color: #ff453a; + box-shadow: 0 0 6px rgba(255, 69, 58, 0.5); + } + + @keyframes pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.7; transform: scale(1.1); } + } + + .status-text { + flex: 1; + } + + .support-btn { + background: none; + border: none; + color: var(--vscode-descriptionForeground); + cursor: pointer; + padding: 2px 4px; + opacity: 0.6; + font-size: 11px; + display: flex; + align-items: center; + gap: 4px; + } + + .support-btn:hover { + opacity: 1; + } + + .status-text .usage-badge { + display: inline-flex; + align-items: center; + gap: 4px; + color: inherit; + text-decoration: none; + background: rgba(255, 255, 255, 0.08); + padding: 2px 8px 2px 8px; + border-radius: 10px; + cursor: pointer; + transition: background 0.15s, transform 0.1s; + } + + .status-text .usage-badge:hover { + background: rgba(255, 255, 255, 0.15); + transform: translateY(-1px); + } + + .status-text .usage-badge:active { + transform: translateY(0); + } + + .status-text .usage-icon { + width: 12px; + height: 12px; + flex-shrink: 0; + } + + pre { + white-space: pre-wrap; + word-wrap: break-word; + margin: 0; + } + + .session-badge { + margin-left: 16px; + background-color: var(--vscode-badge-background); + color: var(--vscode-badge-foreground); + padding: 4px 8px; + border-radius: 12px; + font-size: 11px; + font-weight: 500; + display: flex; + align-items: center; + gap: 4px; + transition: background-color 0.2s, transform 0.1s; + } + + .session-badge:hover { + background-color: var(--vscode-button-hoverBackground); + transform: scale(1.02); } .session-icon { font-size: 10px; } - .session-label { - opacity: 0.8; - font-size: 10px; + .session-label { + opacity: 0.8; + font-size: 10px; + } + + .session-status { + font-size: 12px; + color: var(--vscode-descriptionForeground); + padding: 2px 6px; + border-radius: 4px; + background-color: var(--vscode-badge-background); + border: 1px solid var(--vscode-panel-border); + } + + .session-status.active { + color: var(--vscode-terminal-ansiGreen); + background-color: rgba(0, 210, 106, 0.1); + border-color: var(--vscode-terminal-ansiGreen); + } + + /* Markdown content styles */ + .message h1, .message h2, .message h3, .message h4 { + margin: 0.8em 0 0.4em 0; + font-weight: 600; + line-height: 1.3; + } + + .message h1 { + font-size: 1.5em; + border-bottom: 2px solid var(--vscode-panel-border); + padding-bottom: 0.3em; + } + + .message h2 { + font-size: 1.3em; + border-bottom: 1px solid var(--vscode-panel-border); + padding-bottom: 0.2em; + } + + .message h3 { + font-size: 1.1em; + } + + .message h4 { + font-size: 1.05em; + } + + .message strong { + font-weight: 600; + color: var(--vscode-terminal-ansiBrightWhite); + } + + .message em { + font-style: italic; + } + + .message ul, .message ol { + margin: 0.6em 0; + padding-left: 1.5em; + } + + .message li { + margin: 0.3em 0; + line-height: 1.4; + } + + .message ul li { + list-style-type: disc; + } + + .message ol li { + list-style-type: decimal; + } + + .message p { + margin: 0.5em 0; + line-height: 1.6; + } + + .message p:first-child { + margin-top: 0; + } + + .message p:last-child { + margin-bottom: 0; + } + + .message br { + line-height: 1.2; + } + + .restore-container { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px + } + + .restore-btn { + background-color: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: none; + padding: 4px 10px; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + font-weight: 500; + } + + .restore-btn.dark { + background-color: #2d2d30; + color: #999999; + } + + .restore-btn:hover { + background-color: var(--vscode-button-hoverBackground); + } + + .restore-btn.dark:hover { + background-color: #3e3e42; + } + + .restore-date { + font-size: 10px; + color: var(--vscode-descriptionForeground); + opacity: 0.8; + } + + .conversation-history { + position: absolute; + top: 60px; + left: 0; + right: 0; + bottom: 60px; + background-color: var(--vscode-editor-background); + border: 1px solid var(--vscode-widget-border); + z-index: 1000; + } + + .conversation-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 16px; + border-bottom: 1px solid var(--vscode-widget-border); + } + + .conversation-header h3 { + margin: 0; + font-size: 16px; + } + + .conversation-list { + padding: 8px; + overflow-y: auto; + height: calc(100% - 60px); + } + + .conversation-item { + padding: 12px; + margin: 4px 0; + border: 1px solid var(--vscode-widget-border); + border-radius: 6px; + cursor: pointer; + background-color: var(--vscode-list-inactiveSelectionBackground); + } + + .conversation-item:hover { + background-color: var(--vscode-list-hoverBackground); + } + + .conversation-title { + font-weight: 500; + margin-bottom: 4px; + } + + .conversation-meta { + font-size: 12px; + color: var(--vscode-descriptionForeground); + margin-bottom: 4px; + } + + .conversation-preview { + font-size: 11px; + color: var(--vscode-descriptionForeground); + opacity: 0.8; + } + + /* Tool loading animation */ + .tool-loading { + padding: 16px 12px; + display: flex; + align-items: center; + gap: 12px; + background-color: var(--vscode-panel-background); + border-top: 1px solid var(--vscode-panel-border); + } + + .loading-spinner { + display: flex; + gap: 4px; + } + + .loading-ball { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: var(--vscode-button-background); + animation: bounce 1.4s ease-in-out infinite both; + } + + .loading-ball:nth-child(1) { animation-delay: -0.32s; } + .loading-ball:nth-child(2) { animation-delay: -0.16s; } + .loading-ball:nth-child(3) { animation-delay: 0s; } + + @keyframes bounce { + 0%, 80%, 100% { + transform: scale(0.6); + opacity: 0.5; + } + 40% { + transform: scale(1); + opacity: 1; + } + } + + .loading-text { + font-size: 12px; + color: var(--vscode-descriptionForeground); + font-style: italic; + } + + /* Tool completion indicator */ + .tool-completion { + padding: 8px 12px; + display: flex; + align-items: center; + gap: 6px; + background-color: rgba(76, 175, 80, 0.1); + border-top: 1px solid rgba(76, 175, 80, 0.2); + font-size: 12px; + } + + .completion-icon { + color: #4caf50; + font-weight: bold; + } + + .completion-text { + color: var(--vscode-foreground); + opacity: 0.8; + } + + /* MCP Servers styles */ + .mcp-servers-list { + padding: 4px; + } + + .mcp-server-item { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 12px 16px; + border: 1px solid var(--vscode-panel-border); + border-radius: 8px; + margin-bottom: 8px; + background-color: var(--vscode-editor-background); + transition: all 0.2s ease; + flex-wrap: wrap; + } + + .mcp-server-item:hover { + border-color: var(--vscode-focusBorder); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + } + + .server-info { + flex: 1; + min-width: 0; + } + + .server-name { + font-weight: 600; + font-size: 14px; + color: var(--vscode-foreground); + margin-bottom: 4px; + } + + .server-type { + display: inline-block; + background-color: var(--vscode-badge-background); + color: var(--vscode-badge-foreground); + padding: 4px 8px; + border-radius: 4px; + font-size: 11px; + font-weight: 500; + margin-bottom: 8px; } - .session-status { + .server-config { font-size: 12px; color: var(--vscode-descriptionForeground); - padding: 2px 6px; - border-radius: 4px; - background-color: var(--vscode-badge-background); - border: 1px solid var(--vscode-panel-border); - } - - .session-status.active { - color: var(--vscode-terminal-ansiGreen); - background-color: rgba(0, 210, 106, 0.1); - border-color: var(--vscode-terminal-ansiGreen); + opacity: 0.9; + line-height: 1.4; + word-break: break-all; } - /* Markdown content styles */ - .message h1, .message h2, .message h3, .message h4 { - margin: 0.8em 0 0.4em 0; - font-weight: 600; - line-height: 1.3; + .server-delete-btn { + padding: 4px 10px; + font-size: 12px; + color: var(--vscode-errorForeground); + border-color: var(--vscode-errorForeground); + justify-content: center; } - .message h1 { - font-size: 1.5em; - border-bottom: 2px solid var(--vscode-panel-border); - padding-bottom: 0.3em; + .server-delete-btn:hover { + background-color: var(--vscode-inputValidation-errorBackground); + border-color: var(--vscode-errorForeground); } - .message h2 { - font-size: 1.3em; - border-bottom: 1px solid var(--vscode-panel-border); - padding-bottom: 0.2em; + .server-actions { + display: flex; + gap: 8px; + align-items: center; + flex-shrink: 0; } - .message h3 { - font-size: 1.1em; + .server-edit-btn { + padding: 4px 10px; + font-size: 12px; + color: var(--vscode-foreground); + border-color: var(--vscode-panel-border); + transition: all 0.2s ease; + justify-content: center; } - .message h4 { - font-size: 1.05em; + .server-edit-btn:hover { + background-color: var(--vscode-list-hoverBackground); + border-color: var(--vscode-focusBorder); } - .message strong { - font-weight: 600; - color: var(--vscode-terminal-ansiBrightWhite); + .mcp-add-server { + text-align: center; + margin-bottom: 24px; + padding: 0 4px; } - .message em { - font-style: italic; + .mcp-add-form { + background-color: var(--vscode-editor-background); + border: 1px solid var(--vscode-panel-border); + border-radius: 8px; + padding: 24px; + margin-top: 20px; + box-sizing: border-box; + width: 100%; } - .message ul, .message ol { - margin: 0.6em 0; - padding-left: 1.5em; + .form-group { + margin-bottom: 20px; + box-sizing: border-box; + width: 100%; } - .message li { - margin: 0.3em 0; - line-height: 1.4; + .form-group label { + display: block; + margin-bottom: 6px; + font-weight: 500; + font-size: 13px; + color: var(--vscode-foreground); } - .message ul li { - list-style-type: disc; + .form-group input, + .form-group select, + .form-group textarea { + width: 100%; + max-width: 100%; + padding: 8px 12px; + border: 1px solid var(--vscode-input-border); + border-radius: 4px; + background-color: var(--vscode-input-background); + color: var(--vscode-input-foreground); + font-size: 13px; + font-family: var(--vscode-font-family); + box-sizing: border-box; + resize: vertical; } - .message ol li { - list-style-type: decimal; + .form-group input:focus, + .form-group select:focus, + .form-group textarea:focus { + outline: none; + border-color: var(--vscode-focusBorder); + box-shadow: 0 0 0 1px var(--vscode-focusBorder); } - .message p { - margin: 0.5em 0; - line-height: 1.6; + .form-group textarea { + resize: vertical; + min-height: 60px; } - .message p:first-child { - margin-top: 0; + .form-buttons { + display: flex; + gap: 8px; + justify-content: flex-end; + margin-top: 20px; } - .message p:last-child { + .mcp-add-server { margin-bottom: 0; + padding: 0 4px; } - .message br { - line-height: 1.2; + .mcp-auth-btn { + color: var(--vscode-textLink-foreground); + font-size: 12px; + cursor: pointer; + position: relative; } - .restore-container { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 10px + .mcp-auth-btn:hover { + text-decoration: underline; } - .restore-btn { - background-color: var(--vscode-button-background); - color: var(--vscode-button-foreground); - border: none; - padding: 4px 10px; + .mcp-auth-btn:hover::after { + content: attr(data-tooltip); + position: absolute; + bottom: calc(100% + 6px); + right: 0; + background: var(--vscode-editorHoverWidget-background, #1e1e1e); + color: var(--vscode-editorHoverWidget-foreground, #ccc); + border: 1px solid var(--vscode-editorHoverWidget-border, #454545); + padding: 4px 8px; border-radius: 4px; - cursor: pointer; - font-size: 12px; - font-weight: 500; + font-size: 11px; + white-space: nowrap; + z-index: 100; } - .restore-btn.dark { - background-color: #2d2d30; - color: #999999; + .no-servers { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + padding: 20px 12px; + color: var(--vscode-descriptionForeground); } - .restore-btn:hover { - background-color: var(--vscode-button-hoverBackground); + .no-servers-icon { + opacity: 0.4; } - .restore-btn.dark:hover { - background-color: #3e3e42; + .no-servers-text { + font-size: 13px; } - .restore-date { - font-size: 10px; - color: var(--vscode-descriptionForeground); - opacity: 0.8; + .no-servers-btn { + margin-top: 4px; + font-size: 12px; } - .conversation-history { - position: absolute; - top: 60px; - left: 0; - right: 0; - bottom: 60px; - background-color: var(--vscode-editor-background); - border: 1px solid var(--vscode-widget-border); - z-index: 1000; + /* Popular MCP Servers */ + .mcp-popular-servers { + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid var(--vscode-panel-border); } - .conversation-header { + .mcp-popular-servers h4 { + margin: 0 0 16px 0; + font-size: 14px; + font-weight: 600; + color: var(--vscode-foreground); + opacity: 0.9; + } + + .skill-item-row { display: flex; - justify-content: space-between; align-items: center; - padding: 12px 16px; - border-bottom: 1px solid var(--vscode-widget-border); + gap: 12px; + width: 100%; } - .conversation-header h3 { - margin: 0; - font-size: 16px; + .skill-item-info { + flex: 1; + min-width: 0; + overflow: hidden; } - .conversation-list { - padding: 8px; - overflow-y: auto; - height: calc(100% - 60px); + .skill-item-desc { + font-size: 12px; + color: var(--vscode-descriptionForeground); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } - .conversation-item { - padding: 12px; - margin: 4px 0; - border: 1px solid var(--vscode-widget-border); + .popular-servers-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 12px; + } + + .popular-server-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + background-color: var(--vscode-editor-background); + border: 1px solid var(--vscode-panel-border); border-radius: 6px; cursor: pointer; - background-color: var(--vscode-list-inactiveSelectionBackground); + transition: all 0.2s ease; } - .conversation-item:hover { + .popular-server-item:hover { + border-color: var(--vscode-focusBorder); background-color: var(--vscode-list-hoverBackground); + transform: translateY(-1px); } - .conversation-title { - font-weight: 500; - margin-bottom: 4px; + .popular-server-icon { + font-size: 24px; + flex-shrink: 0; } - .conversation-meta { - font-size: 12px; - color: var(--vscode-descriptionForeground); - margin-bottom: 4px; + .popular-server-info { + flex: 1; + min-width: 0; } - .conversation-preview { + .popular-server-name { + font-weight: 600; + font-size: 13px; + color: var(--vscode-foreground); + margin-bottom: 2px; + } + + .popular-server-desc { font-size: 11px; color: var(--vscode-descriptionForeground); opacity: 0.8; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } - /* Tool loading animation */ - .tool-loading { - padding: 16px 12px; + /* MCP Tabs */ + .mcp-tabs { display: flex; - align-items: center; - gap: 12px; - background-color: var(--vscode-panel-background); - border-top: 1px solid var(--vscode-panel-border); + gap: 0; } - .loading-spinner { - display: flex; - gap: 4px; + .mcp-tab { + background: none; + border: none; + color: var(--vscode-descriptionForeground); + font-size: 14px; + font-weight: 500; + cursor: pointer; + padding: 4px 12px; + border-bottom: 2px solid transparent; + transition: all 0.15s ease; } - .loading-ball { - width: 8px; - height: 8px; - border-radius: 50%; - background-color: var(--vscode-button-background); - animation: bounce 1.4s ease-in-out infinite both; + .mcp-tab:hover { + color: var(--vscode-foreground); } - .loading-ball:nth-child(1) { animation-delay: -0.32s; } - .loading-ball:nth-child(2) { animation-delay: -0.16s; } - .loading-ball:nth-child(3) { animation-delay: 0s; } - - @keyframes bounce { - 0%, 80%, 100% { - transform: scale(0.6); - opacity: 0.5; - } - 40% { - transform: scale(1); - opacity: 1; - } + .mcp-tab.active { + color: var(--vscode-foreground); + border-bottom-color: var(--vscode-button-background); } - .loading-text { - font-size: 12px; - color: var(--vscode-descriptionForeground); - font-style: italic; + /* MCP Marketplace */ + .marketplace-search { + padding: 0 0 12px 0; } - /* Tool completion indicator */ - .tool-completion { + .marketplace-search input { + width: 100%; padding: 8px 12px; - display: flex; - align-items: center; - gap: 6px; - background-color: rgba(76, 175, 80, 0.1); - border-top: 1px solid rgba(76, 175, 80, 0.2); - font-size: 12px; + font-size: 13px; + font-family: var(--vscode-font-family); + background-color: var(--vscode-input-background); + color: var(--vscode-input-foreground); + border: 1px solid var(--vscode-input-border, var(--vscode-panel-border)); + border-radius: 6px; + outline: none; + box-sizing: border-box; } - .completion-icon { - color: #4caf50; - font-weight: bold; + .marketplace-search input:focus { + border-color: var(--vscode-focusBorder); } - .completion-text { - color: var(--vscode-foreground); - opacity: 0.8; + .marketplace-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: 10px; } - /* MCP Servers styles */ - .mcp-servers-list { - padding: 4px; + .marketplace-item { + padding: 12px; + background-color: var(--vscode-editor-background); + border: 1px solid var(--vscode-panel-border); + border-radius: 6px; + cursor: pointer; + transition: all 0.15s ease; } - .mcp-server-item { + .marketplace-item:hover { + border-color: var(--vscode-focusBorder); + background-color: var(--vscode-list-hoverBackground); + transform: translateY(-1px); + } + + .marketplace-item-header { display: flex; align-items: center; - justify-content: space-between; - padding: 20px 24px; - border: 1px solid var(--vscode-panel-border); - border-radius: 8px; - margin-bottom: 16px; - background-color: var(--vscode-editor-background); - transition: all 0.2s ease; + gap: 10px; + margin-bottom: 6px; } - .mcp-server-item:hover { - border-color: var(--vscode-focusBorder); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + .marketplace-item-icon { + width: 28px; + height: 28px; + border-radius: 6px; + flex-shrink: 0; + object-fit: cover; } - .server-info { + .marketplace-item-icon-placeholder { + width: 28px; + height: 28px; + border-radius: 6px; + flex-shrink: 0; + background-color: rgba(128, 128, 128, 0.15); + display: flex; + align-items: center; + justify-content: center; + font-size: 13px; + font-weight: 600; + color: var(--vscode-descriptionForeground); + } + + .marketplace-item-info { flex: 1; + min-width: 0; + display: flex; + align-items: center; + gap: 6px; } - .server-name { + .marketplace-item-name { font-weight: 600; - font-size: 16px; + font-size: 13px; color: var(--vscode-foreground); - margin-bottom: 8px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } - .server-type { - display: inline-block; - background-color: var(--vscode-badge-background); - color: var(--vscode-badge-foreground); - padding: 4px 8px; - border-radius: 4px; - font-size: 11px; - font-weight: 500; - margin-bottom: 8px; + .marketplace-item-type { + font-size: 9px; + padding: 1px 5px; + border-radius: 3px; + background-color: rgba(128, 128, 128, 0.15); + color: var(--vscode-descriptionForeground); + flex-shrink: 0; } - .server-config { - font-size: 13px; + .marketplace-item-desc { + font-size: 11px; color: var(--vscode-descriptionForeground); - opacity: 0.9; line-height: 1.4; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; } - .server-delete-btn { - padding: 8px 16px; - font-size: 13px; - color: var(--vscode-errorForeground); - border-color: var(--vscode-errorForeground); - min-width: 80px; - justify-content: center; + .marketplace-item-meta { + display: flex; + align-items: center; + gap: 8px; + margin-top: 2px; } - .server-delete-btn:hover { - background-color: var(--vscode-inputValidation-errorBackground); - border-color: var(--vscode-errorForeground); + .marketplace-item-stars { + font-size: 11px; + color: var(--vscode-descriptionForeground); } - .server-actions { + .marketplace-item-lang { display: flex; - gap: 8px; align-items: center; - flex-shrink: 0; + gap: 4px; + font-size: 11px; + color: var(--vscode-descriptionForeground); } - .server-edit-btn { - padding: 8px 16px; - font-size: 13px; - color: var(--vscode-foreground); - border-color: var(--vscode-panel-border); - min-width: 80px; - transition: all 0.2s ease; - justify-content: center; + .lang-dot { + width: 8px; + height: 8px; + border-radius: 50%; + display: inline-block; } - .server-edit-btn:hover { - background-color: var(--vscode-list-hoverBackground); - border-color: var(--vscode-focusBorder); + .marketplace-item-license { + font-size: 10px; + color: var(--vscode-descriptionForeground); } - .mcp-add-server { - text-align: center; - margin-bottom: 24px; - padding: 0 4px; + .marketplace-detail-meta { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 12px; } - .mcp-add-form { - background-color: var(--vscode-editor-background); - border: 1px solid var(--vscode-panel-border); - border-radius: 8px; - padding: 24px; - margin-top: 20px; - box-sizing: border-box; - width: 100%; + .marketplace-detail-link { + color: var(--vscode-textLink-foreground); + font-size: 12px; + text-decoration: none; } - .form-group { - margin-bottom: 20px; - box-sizing: border-box; - width: 100%; + .marketplace-detail-link:hover { + text-decoration: underline; } - .form-group label { - display: block; - margin-bottom: 6px; - font-weight: 500; - font-size: 13px; - color: var(--vscode-foreground); + .marketplace-detail-install { + margin: 12px 0; } - .form-group input, - .form-group select, - .form-group textarea { - width: 100%; - max-width: 100%; - padding: 8px 12px; - border: 1px solid var(--vscode-input-border); - border-radius: 4px; - background-color: var(--vscode-input-background); - color: var(--vscode-input-foreground); + .marketplace-loading { + text-align: center; + padding: 24px; + color: var(--vscode-descriptionForeground); font-size: 13px; - font-family: var(--vscode-font-family); - box-sizing: border-box; - resize: vertical; } - .form-group input:focus, - .form-group select:focus, - .form-group textarea:focus { - outline: none; - border-color: var(--vscode-focusBorder); - box-shadow: 0 0 0 1px var(--vscode-focusBorder); + .marketplace-load-more { + text-align: center; + padding: 12px 0; + } + + .marketplace-detail { + padding: 4px 0; + } + + .marketplace-back-btn { + background: none; + border: none; + color: var(--vscode-textLink-foreground); + font-size: 12px; + cursor: pointer; + padding: 0 0 12px 0; + display: block; + } + + .marketplace-back-btn:hover { + text-decoration: underline; } - .form-group textarea { - resize: vertical; - min-height: 60px; + .marketplace-detail-header { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 14px; } - .form-buttons { - display: flex; - gap: 8px; - justify-content: flex-end; - margin-top: 20px; + .marketplace-detail-icon { + width: 36px; + height: 36px; + border-radius: 8px; + object-fit: cover; } - .no-servers { - text-align: center; - color: var(--vscode-descriptionForeground); - font-style: italic; - padding: 40px 20px; + .marketplace-detail-header-info { + flex: 1; + min-width: 0; } - /* Popular MCP Servers */ - .mcp-popular-servers { - margin-top: 32px; - padding-top: 24px; - border-top: 1px solid var(--vscode-panel-border); + .marketplace-detail-header-meta { + display: flex; + align-items: center; + gap: 8px; + margin-top: 2px; } - .mcp-popular-servers h4 { - margin: 0 0 16px 0; - font-size: 14px; + .marketplace-detail-name { + font-size: 15px; font-weight: 600; color: var(--vscode-foreground); - opacity: 0.9; } - .popular-servers-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 12px; + .marketplace-install-btn { + flex-shrink: 0; + align-self: center; } - .popular-server-item { - display: flex; - align-items: center; - gap: 12px; - padding: 12px 16px; + .marketplace-detail-desc { + font-size: 13px; + color: var(--vscode-descriptionForeground); + line-height: 1.5; + margin-bottom: 14px; + } + + .marketplace-detail-config { background-color: var(--vscode-editor-background); border: 1px solid var(--vscode-panel-border); border-radius: 6px; - cursor: pointer; - transition: all 0.2s ease; + padding: 10px 12px; } - .popular-server-item:hover { - border-color: var(--vscode-focusBorder); - background-color: var(--vscode-list-hoverBackground); - transform: translateY(-1px); + .marketplace-detail-section-title { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--vscode-descriptionForeground); + margin-bottom: 8px; } - .popular-server-icon { - font-size: 24px; - flex-shrink: 0; + .marketplace-detail-row { + font-size: 12px; + color: var(--vscode-descriptionForeground); + margin-bottom: 4px; } - .popular-server-info { - flex: 1; - min-width: 0; + .marketplace-detail-row code, + .marketplace-detail-env code { + background-color: var(--vscode-textCodeBlock-background); + padding: 2px 6px; + border-radius: 3px; + font-family: var(--vscode-editor-font-family); + font-size: 11px; } - .popular-server-name { - font-weight: 600; - font-size: 13px; + .detail-label { color: var(--vscode-foreground); - margin-bottom: 2px; + font-weight: 500; } - .popular-server-desc { - font-size: 11px; + .marketplace-detail-env { + font-size: 12px; color: var(--vscode-descriptionForeground); - opacity: 0.8; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + margin-left: 12px; + margin-bottom: 2px; } /* Processing indicator - morphing orange dot */ @@ -3054,7 +4650,6 @@ const styles = ` background: var(--vscode-editor-background); border: 1px solid var(--vscode-widget-border, var(--vscode-panel-border)); border-radius: 12px; - width: 320px; padding: 32px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); animation: installFadeIn 0.2s ease-out; @@ -3090,6 +4685,7 @@ const styles = ` .install-body { text-align: center; + margin-top: 20px; } .install-main { @@ -3238,6 +4834,221 @@ const styles = ` color: var(--vscode-descriptionForeground); } + .install-options { + display: flex; + flex-direction: column; + gap: 10px; + width: 100%; + margin-top: 8px; + } + + .install-option { + width: 100%; + padding: 14px 16px; + background: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: none; + border-radius: 8px; + cursor: pointer; + transition: all 0.15s; + text-align: left; + display: flex; + flex-direction: column; + gap: 2px; + } + + .install-option:hover { + background: var(--vscode-button-hoverBackground); + transform: translateY(-1px); + } + + .install-option-secondary { + background: transparent; + border: 1px solid var(--vscode-widget-border, var(--vscode-panel-border)); + color: var(--vscode-foreground); + } + + .install-option-secondary:hover { + background: var(--vscode-list-hoverBackground); + border-color: var(--vscode-focusBorder); + } + + .install-option-title { + font-size: 14px; + font-weight: 500; + } + + .install-option-desc { + font-size: 12px; + opacity: 0.8; + } + + .install-funds { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + padding: 12px 0; + } + + .install-funds-title { + margin: 0; + font-size: 16px; + font-weight: 600; + color: var(--vscode-foreground); + margin-bottom: 10px; + } + + .install-funds-hint { + margin: 0; + font-size: 13px; + color: var(--vscode-descriptionForeground); + margin-bottom: 10px; + } + + .install-amounts { + display: flex; + flex-wrap: wrap; + gap: 8px; + width: 100%; + } + + .install-amount { + flex: 1 1 calc(33.333% - 6px); + min-width: 60px; + padding: 12px 8px; + font-size: 14px; + font-weight: 600; + background: var(--vscode-input-background); + color: var(--vscode-foreground); + border: 1px solid var(--vscode-widget-border, var(--vscode-panel-border)); + border-radius: 8px; + cursor: pointer; + transition: all 0.15s; + } + + .install-amount:hover { + border-color: var(--vscode-focusBorder); + background: var(--vscode-list-hoverBackground); + } + + .install-custom-amount { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + margin-top: 4px; + } + + .install-custom-currency { + font-size: 14px; + font-weight: 600; + color: var(--vscode-descriptionForeground); + } + + .install-custom-input { + flex: 1; + padding: 10px 12px; + font-size: 14px; + background: var(--vscode-input-background); + color: var(--vscode-foreground); + border: 1px solid var(--vscode-widget-border, var(--vscode-panel-border)); + border-radius: 6px; + outline: none; + } + + .install-custom-input:focus { + border-color: var(--vscode-focusBorder); + } + + .install-custom-input::placeholder { + color: var(--vscode-descriptionForeground); + } + + .install-custom-btn { + padding: 10px 16px; + font-size: 13px; + font-weight: 500; + background: var(--vscode-button-background); + color: var(--vscode-button-foreground); + border: none; + border-radius: 6px; + cursor: pointer; + transition: background 0.15s; + } + + .install-custom-btn:hover { + background: var(--vscode-button-hoverBackground); + } + + .install-powered-by { + font-size: 11px; + color: var(--vscode-descriptionForeground); + margin-top: 8px; + } + + .install-powered-by a { + color: var(--vscode-textLink-foreground); + text-decoration: none; + } + + .install-powered-by a:hover { + text-decoration: underline; + } + + .install-back-btn { + background: none; + border: none; + color: var(--vscode-textLink-foreground); + font-size: 13px; + cursor: pointer; + padding: 8px; + } + + .install-back-btn:hover { + text-decoration: underline; + } + + /* Toast notifications */ + .toast-notification { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + background: linear-gradient(135deg, #10b981, #059669); + color: white; + padding: 10px 20px; + border-radius: 8px; + font-size: 12px; + font-weight: 500; + z-index: 10000; + animation: toastSlideUp 0.3s ease; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + } + + .toast-notification.fade-out { + opacity: 0; + transform: translateX(-50%) translateY(10px); + transition: all 0.3s ease; + } + + @keyframes toastSlideUp { + from { + transform: translateX(-50%) translateY(20px); + opacity: 0; + } + to { + transform: translateX(-50%) translateY(0); + opacity: 1; + } + } + + /* OpenCredits balance badge style */ + .opencredits-balance { + background: linear-gradient(135deg, rgba(16, 185, 129, 0.15), rgba(5, 150, 105, 0.15)) !important; + color: #10b981 !important; + } + ` export default styles \ No newline at end of file diff --git a/src/ui.ts b/src/ui.ts index b572cc9..321f2f0 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -1,12 +1,19 @@ import getScript from './script'; import styles from './ui-styles' +import recommendedModels from './recommended-models.json' +import topMcpServers from './top-mcp-servers.json' +import topSkills from './top-skills.json' +import topPlugins from './top-plugins.json' +import getSkillsHtml from './skills-ui' +import getPluginsHtml from './plugins-ui' -const getHtml = (isTelemetryEnabled: boolean) => ` +const getHtml = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'https://ccc.api.opencredits.ai', opencreditsWebUrl: string = 'https://ccc.opencredits.ai', opencreditsPublishableKey: string = 'oc_pk_c43da4f9a9484ae484ad29bc97cc354f', editorName: string = 'unknown', extensionVersion: string = 'unknown') => ` + Claude Code Chat ${styles} @@ -57,33 +64,48 @@ const getHtml = (isTelemetryEnabled: boolean) => `
-
-
- Plan First -
-
-
- Thinking Mode -
+
+ + +
+
+
- - +
+ + +
+ +
@@ -102,21 +124,19 @@ const getHtml = (isTelemetryEnabled: boolean) => ` +
@@ -127,11 +147,9 @@ const getHtml = (isTelemetryEnabled: boolean) => `
Initializing...
-
@@ -163,54 +181,15 @@ const getHtml = (isTelemetryEnabled: boolean) => `
-
- -
+
+ + +
+
+ + + + + + @@ -497,6 +808,9 @@ const getHtml = (isTelemetryEnabled: boolean) => ` + ${getSkillsHtml()} + ${getPluginsHtml()} + - ${getScript(isTelemetryEnabled)} + + ${getScript(isTelemetryEnabled, opencreditsApiUrl, opencreditsWebUrl, opencreditsPublishableKey)} - - ${isTelemetryEnabled ? '' : ''} + ${isTelemetryEnabled ? '' : ''} `; -export default getHtml; \ No newline at end of file +export default getHtml; diff --git a/tsconfig.json b/tsconfig.json index 78af3c7..1f7ad51 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,8 +6,10 @@ "lib": [ "ES2022" ], + "types": ["node", "mocha"], "sourceMap": true, "rootDir": "src", + "resolveJsonModule": true, "strict": true, /* enable all strict type-checking options */ /* Additional Checks */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ @@ -16,6 +18,7 @@ }, "exclude": [ "mcp-permissions.js", - "claude-code-chat-permissions-mcp" + "claude-code-chat-permissions-mcp", + "backup-files" ] } From 7e727a53cb71ce5e25bc12d171acf409374d16a1 Mon Sep 17 00:00:00 2001 From: onderakbulut Date: Wed, 6 May 2026 19:04:51 +0300 Subject: [PATCH 7/7] Update publisher name in package.json and remove notification settings from UI --- package.json | 2 +- src/ui.ts | 20 -------------------- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/package.json b/package.json index ae23d4e..f42c9cc 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Chat for Claude Code", "description": "Beautiful Claude Code Chat Interface for VS Code", "version": "2.0.8", - "publisher": "AndrePimenta", + "publisher": "OnderAkbulut", "author": "Andre Pimenta", "repository": { "type": "git", diff --git a/src/ui.ts b/src/ui.ts index 321f2f0..be18286 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -392,26 +392,6 @@ const getHtml = (isTelemetryEnabled: boolean, opencreditsApiUrl: string = 'https -

Notifications

-
-

- Configure notification preferences when Claude completes responses. -

-
-
-
- - -
-
- - -

- Leave empty to use default Windows Generic sound. Supports .wav files. -

-
-
-

Customize Claude Command