mirror of
https://github.com/andrepimenta/claude-code-chat.git
synced 2026-06-01 17:45:30 +08:00
Compare commits
123 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c11224310d | ||
|
|
cb5943eec5 | ||
|
|
3d8bcf5241 | ||
|
|
474910a330 | ||
|
|
58de99030c | ||
|
|
6d112012b2 | ||
|
|
e31c5357d2 | ||
|
|
b3ff6e9c03 | ||
|
|
05a15d19a6 | ||
|
|
f5d4c7851b | ||
|
|
ef0b3c1b4e | ||
|
|
deca7de8d5 | ||
|
|
b527b6f4c9 | ||
|
|
2f792e7158 | ||
|
|
97920395d1 | ||
|
|
2e640fa20a | ||
|
|
5136985474 | ||
|
|
683148c4cf | ||
|
|
e18fa5e261 | ||
|
|
63299008d0 | ||
|
|
14ac46018f | ||
|
|
a156881a08 | ||
|
|
0764bf8202 | ||
|
|
82899ebb40 | ||
|
|
abf81a1176 | ||
|
|
da46d5e3d9 | ||
|
|
d20d8667f3 | ||
|
|
6c37394015 | ||
|
|
2b1ad70f6b | ||
|
|
bf527bb922 | ||
|
|
df8188380d | ||
|
|
79a0b6b4b2 | ||
|
|
dd47efec04 | ||
|
|
d891070d9e | ||
|
|
1be89d43a4 | ||
|
|
0abfab72a8 | ||
|
|
1eacc6ff74 | ||
|
|
031a2c5fc3 | ||
|
|
73c4a38da1 | ||
|
|
53acc0a79f | ||
|
|
62163dbc32 | ||
|
|
d225ff2596 | ||
|
|
ab5c393253 | ||
|
|
d6a73a1a7f | ||
|
|
5abb1fedd9 | ||
|
|
3b534cfce2 | ||
|
|
6bd906981b | ||
|
|
4f126641e4 | ||
|
|
2d63eaac58 | ||
|
|
f44dc28763 | ||
|
|
2c47349282 | ||
|
|
43c1c85efb | ||
|
|
b07857bf57 | ||
|
|
c9677b6185 | ||
|
|
2053e768a8 | ||
|
|
980d19bcb2 | ||
|
|
8a581908e3 | ||
|
|
2feaed600d | ||
|
|
826c25bdd6 | ||
|
|
3e8a9630bd | ||
|
|
00f4e272b6 | ||
|
|
2aa7db86e7 | ||
|
|
42a5ebf763 | ||
|
|
5b671485a1 | ||
|
|
6e9893e3f3 | ||
|
|
5c33bc94c1 | ||
|
|
1681bea37f | ||
|
|
b8e5c253a3 | ||
|
|
2eceda51ed | ||
|
|
cba6138828 | ||
|
|
03b0eb96fe | ||
|
|
cf46551f00 | ||
|
|
857d55e8d1 | ||
|
|
ae9ad4a794 | ||
|
|
83584fff60 | ||
|
|
521f9a7d68 | ||
|
|
4f5a4cb7ef | ||
|
|
234516c1ef | ||
|
|
b261f5cb8a | ||
|
|
88a2ba71a1 | ||
|
|
717284a979 | ||
|
|
d9baf71e4a | ||
|
|
bad8c9a0a8 | ||
|
|
2e4c866da2 | ||
|
|
1fa94b9c54 | ||
|
|
3ec983188a | ||
|
|
0b98538903 | ||
|
|
44166defce | ||
|
|
ddf83cf760 | ||
|
|
63acf5e7f9 | ||
|
|
ede4fbaf98 | ||
|
|
06eb335f7b | ||
|
|
f501d2ddc4 | ||
|
|
586b004273 | ||
|
|
b2579ed0f8 | ||
|
|
0bdb0ce30a | ||
|
|
bb20bb29c5 | ||
|
|
5a59d67021 | ||
|
|
7649c9aaab | ||
|
|
2d3c12ca38 | ||
|
|
c5486c6e26 | ||
|
|
5168aa0333 | ||
|
|
581ad59ea2 | ||
|
|
4c57af4141 | ||
|
|
64767e3850 | ||
|
|
be8be4c700 | ||
|
|
f603b4a298 | ||
|
|
e6bdc80946 | ||
|
|
dcd49458cb | ||
|
|
0129cd1bc9 | ||
|
|
6dfdc24500 | ||
|
|
e97062ae03 | ||
|
|
8b0bc2904b | ||
|
|
bc7fa07e3a | ||
|
|
cd2b563be4 | ||
|
|
4bab912554 | ||
|
|
c3645e5d6b | ||
|
|
3bd35f0685 | ||
|
|
a1c2e6d139 | ||
|
|
e6681c4ae1 | ||
|
|
300e569c96 | ||
|
|
cedde172bf | ||
|
|
1dca03a3ec |
5
.claude/settings.json
Normal file
5
.claude/settings.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"enabledPlugins": {
|
||||
"frontend-design@claude-plugins-official": true
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(npm run compile:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(sed:*)",
|
||||
"Bash(rg:*)",
|
||||
"Bash(npx tsc:*)"
|
||||
],
|
||||
"deny": []
|
||||
},
|
||||
"enableAllProjectMcpServers": false
|
||||
}
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,4 +3,6 @@ dist
|
||||
node_modules
|
||||
.vscode-test/
|
||||
*.vsix
|
||||
backup
|
||||
backup
|
||||
backup-files
|
||||
.claude/settings.local.json
|
||||
|
||||
@@ -9,3 +9,10 @@ vsc-extension-quickstart.md
|
||||
**/*.map
|
||||
**/*.ts
|
||||
**/.vscode-test.*
|
||||
backup
|
||||
.claude
|
||||
claude-code-chat-permissions-mcp/**
|
||||
node_modules
|
||||
mcp-permissions.js
|
||||
backup-files
|
||||
build
|
||||
|
||||
311
CHANGELOG.md
311
CHANGELOG.md
@@ -4,6 +4,317 @@ All notable changes to the "claude-code-chat" extension will be documented in th
|
||||
|
||||
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
|
||||
|
||||
## [2.0.6] - 2026-04-23
|
||||
|
||||
### 🚀 Features Added
|
||||
- **Smarter post-install setup**: Fresh installs now "just work" without a VS Code restart. After install, the extension checks whether `claude` resolved on your PATH and, if not, auto-configures `claudeCodeChat.executable.path` to the known install location. An existing custom executable path is respected.
|
||||
- **WSL: Node.js path is now optional**: Recent Claude Code ships as a native binary and doesn't need Node. Leave the **Node.js Path** field blank unless you installed Claude via npm. The WSL settings panel was also reordered so **Claude Path** comes first.
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
- **Rock-solid terminals across shells**: Login, Model, /usage, and slash-command terminals now launch Claude directly instead of sending text through the shell. Fixes a class of quoting issues on Windows PowerShell and keeps behavior identical across PowerShell, cmd, bash, and zsh.
|
||||
|
||||
### 🔧 Technical Improvements
|
||||
- Terminal sites now use `createTerminal`'s `shellPath`/`shellArgs` — no shell quoting, consistent env inheritance, identical behavior across OSes.
|
||||
|
||||
## [2.0.4] - 2026-04-21
|
||||
|
||||
### 🚀 Features Added
|
||||
- **Plan Mode (Improved)**:
|
||||
- Plans now render as beautifully formatted markdown with headings, lists, and code blocks
|
||||
- Suggested actions shown as clickable buttons below the plan (e.g. "run npm build")
|
||||
- Permission prompt says "Approve the plan above?" with an Approve button instead of the generic tool approval
|
||||
- **MCP, Skills & Plugins Marketplace**:
|
||||
- Browse 30+ curated MCP servers (GitHub, Slack, Stripe, Notion, Supabase, etc.)
|
||||
- Search across both add-mcp curated and official Anthropic registries with smart ranking
|
||||
- Install MCP servers to project (`.mcp.json`) or global (`~/.claude.json`)
|
||||
- Skills marketplace with one-click install via `npx skills add`
|
||||
- Plugins marketplace to extend Claude Code
|
||||
- OAuth authentication support — open terminal to log in to MCPs
|
||||
- **150+ AI Models via OpenCredits**:
|
||||
- Quick model switching: GPT, Gemini, MiniMax, Kimi, GLM, DeepSeek buttons above the text box
|
||||
- Browse and select from 150+ models across providers
|
||||
- Pay-as-you-go with OpenCredits — no subscription needed
|
||||
- US & EU provider filtering option in settings
|
||||
- Model selection persists correctly after checkout
|
||||
- **Image Preview**:
|
||||
- Paste or pick images with thumbnail preview before sending
|
||||
- Remove attached images before sending
|
||||
- Multiple image attachments per message
|
||||
- Image paths in text auto-detected and sent as base64
|
||||
- **Support & Feedback**:
|
||||
- "Support" button in status bar to send bug reports and feature requests
|
||||
- Submissions sent directly to Discord
|
||||
|
||||
### 🎨 UI Improvements
|
||||
- Inline stop button replaces send button during processing
|
||||
- Self-hosted Umami analytics with editor tracking (VS Code vs Cursor)
|
||||
- BETA badge on model section with instant tooltip
|
||||
- Cleaner model selector and Browse All Models alignment
|
||||
|
||||
### 🐛 Bug Fixes & Reliability
|
||||
- Fix model not being selected after OpenCredits checkout
|
||||
- Fix provider choice modal appearing unexpectedly after settings changes
|
||||
- Fix duplicate login error toast
|
||||
- Fix WSL environment variable passthrough for OpenCredits
|
||||
- Fix Windows URL opening with `start` command
|
||||
- Fix `--mcp-config` error on fresh installs
|
||||
- Await `setEnvsDisabled` so settings reflect changes immediately
|
||||
- Skip npx install prompt with `-y` flag for skills
|
||||
- Better install error messages (Node.js 18+ requirement)
|
||||
- Add node and mocha types to tsconfig for clean editor diagnostics
|
||||
- Remove debug `console.log`s, add `console.error` to empty catch blocks
|
||||
|
||||
## [1.1.0] - 2025-12-06
|
||||
|
||||
### 🚀 Features Added
|
||||
- **Install Modal**: Added installation flow for users without Claude Code CLI
|
||||
- Auto-detects when Claude Code is not installed
|
||||
- One-click installation with progress indicator
|
||||
- Platform-specific installation commands
|
||||
- **Diff Viewer Improvements**:
|
||||
- Show full diff in Edit, MultiEdit, and Write tool use messages
|
||||
- Add "Open Diff" button to open VS Code's native side-by-side diff editor
|
||||
- Add truncation with expand button for long diffs
|
||||
- Optimize diff storage and improve Open Diff button behavior
|
||||
- **Processing Indicator**: New morphing orange dot animation while Claude is working
|
||||
- **Subscription Detection**: Added usage badge to status bar showing plan type (Pro, Max) or API cost
|
||||
- **Conversation Compacting**: Handle `/compact` command in chat with status messages and token reset
|
||||
- **Permission System**: Migrated from MCP file-based to stdio-based permission prompts
|
||||
- **Plan Mode**: Now uses native `--permission-mode plan` CLI flag for cleaner implementation
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
- Fixed diff line alignment by removing ::before pseudo-elements
|
||||
- Fixed auto-scroll for diff tool results
|
||||
- Strip tool_use_error tags from error messages
|
||||
- Improved process termination handling
|
||||
|
||||
### 🔧 Technical Improvements
|
||||
- Run /compact command in chat instead of spawning terminal
|
||||
- Improved terminal and UI experience
|
||||
- Updated diff icon colors
|
||||
|
||||
### 📊 Analytics
|
||||
- Added Umami analytics events to track install flow (modal shown, started, success/failed)
|
||||
|
||||
## [1.0.7] - 2025-10-01
|
||||
|
||||
### 🚀 Features Added
|
||||
- **Slash Commands Update**: Added 4 new slash commands to the commands modal
|
||||
- `/add-dir` - Add additional working directories
|
||||
- `/agents` - Manage custom AI subagents for specialized tasks
|
||||
- `/rewind` - Rewind the conversation and/or code
|
||||
- `/usage` - Show plan usage limits and rate limit status (subscription plans only)
|
||||
|
||||
### 📚 Documentation Updates
|
||||
- Updated slash commands count from 19+ to 23+ built-in commands
|
||||
- Enhanced command descriptions for better clarity:
|
||||
- `/config` - Now specifies "Open the Settings interface (Config tab)"
|
||||
- `/cost` - Added note about cost tracking guide for subscription-specific details
|
||||
- `/status` - Expanded description to mention version, model, account, and connectivity
|
||||
- `/terminal-setup` - Added clarification about iTerm2 and VSCode only support
|
||||
|
||||
## [1.0.6] - 2025-08-26
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
- Fixed typo in codebase
|
||||
- Removed priority settings that were no longer needed
|
||||
|
||||
### 🔧 Technical Improvements
|
||||
- Moved script to separate file for better code organization
|
||||
|
||||
## [1.0.5] - 2025-07-30
|
||||
|
||||
### 🚀 Features Added
|
||||
- **MCP Integration**: Added claude-code-chat-permissions-mcp folder for enhanced permission management
|
||||
- **Message Persistence**: Save message in text box for better user experience
|
||||
- **UI Improvements**: Always display history and new chat options
|
||||
- **Input Enhancement**: Removed maxlength limit for custom command prompt textarea
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
- Fixed new chat functionality
|
||||
- Fixed request start time isProcessing issue
|
||||
- Fixed close and open conversation behavior
|
||||
|
||||
### 🔄 Merged Pull Requests
|
||||
- Merged PR #87 from horatio-sans-serif/main
|
||||
|
||||
## [1.0.4] - 2025-01-22
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
- Fixed input text area overflow issue by adding `box-sizing: border-box` to prevent padding from extending beyond container width
|
||||
- Fixed command parameter handling for `claude-code-chat.openChat` to properly handle both ViewColumn and Uri parameters from different invocation contexts
|
||||
|
||||
### 🔧 Technical Improvements
|
||||
- Enhanced `show()` method to accept optional ViewColumn parameter with ViewColumn.Two as default
|
||||
- Added proper type checking for command parameters to handle context menu invocations
|
||||
- Improved webview panel positioning with flexible column parameter support
|
||||
|
||||
### 🎨 UI/UX Improvements
|
||||
- Resolved text input container sizing issues that caused visual overflow
|
||||
- Better input field styling consistency across different VS Code themes
|
||||
|
||||
## [1.0.0] - 2025-01-15
|
||||
|
||||
### 🚀 Major Features Added
|
||||
|
||||
#### **Advanced Permissions Management System**
|
||||
- Complete permissions framework with MCP integration for secure tool execution
|
||||
- Interactive permission dialogs with detailed tool information and command previews
|
||||
- "Always Allow" functionality with smart command pattern matching for common tools (npm, git, docker, etc.)
|
||||
- YOLO mode for power users to skip all permission checks
|
||||
- Comprehensive permissions settings UI with ability to add/remove specific permissions
|
||||
- File system watcher for real-time permission request handling
|
||||
- Workspace-specific permission storage and management
|
||||
|
||||
#### **MCP (Model Context Protocol) Server Management**
|
||||
- Complete MCP server configuration interface
|
||||
- Popular MCP servers gallery with one-click installation
|
||||
- Custom MCP server creation with validation
|
||||
- Server management (edit, delete, enable/disable)
|
||||
- Automatic permissions server integration
|
||||
- WSL path conversion for cross-platform compatibility
|
||||
|
||||
#### **Sidebar Integration & Multi-Panel Support**
|
||||
- Native VS Code sidebar view with full chat functionality
|
||||
- Smart panel management (auto-close main panel when sidebar opens)
|
||||
- Persistent session state across panel switches
|
||||
- Proper webview lifecycle management
|
||||
- Activity bar integration with custom icon
|
||||
|
||||
#### **Image & Clipboard Enhancements**
|
||||
- Drag-and-drop image support directly into chat
|
||||
- Clipboard image paste functionality (Ctrl+V for screenshots)
|
||||
- Multiple image selection with VS Code's native file picker
|
||||
- Automatic image organization in `.claude/claude-code-chat-images/` folder
|
||||
- Automatic `.gitignore` creation for image folders
|
||||
- Support for PNG, JPG, JPEG, GIF, SVG, WebP, BMP formats
|
||||
|
||||
#### **Code Block & Syntax Improvements**
|
||||
- Enhanced markdown parsing with proper code block detection
|
||||
- Syntax highlighting for code blocks with language detection
|
||||
- Copy-to-clipboard functionality for code blocks
|
||||
- Improved inline code rendering
|
||||
- Better handling of technical identifiers and underscores
|
||||
|
||||
### 🎨 UI/UX Improvements
|
||||
|
||||
#### **Settings & Configuration**
|
||||
- Comprehensive settings modal with organized sections
|
||||
- YOLO mode toggle with visual warnings and explanations
|
||||
- Real-time settings synchronization between UI and VS Code config
|
||||
- Better visual hierarchy and professional styling
|
||||
- Smart configuration validation and error handling
|
||||
|
||||
#### **Message & Chat Interface**
|
||||
- Improved message spacing and visual consistency
|
||||
- Enhanced tool result display with better formatting
|
||||
- Smart scrolling behavior (only auto-scroll if user is at bottom)
|
||||
- Loading indicators and processing states
|
||||
- Better error handling and user feedback
|
||||
|
||||
#### **YOLO Mode Visual Design**
|
||||
- Less subtle YOLO mode section (increased opacity and size)
|
||||
- Changed warning icon from ⚠️ to 🚀 for less intimidating appearance
|
||||
- Soft tomato red styling that's noticeable but not scary
|
||||
- Clear explanation of YOLO mode functionality
|
||||
|
||||
### 🔧 Technical Enhancements
|
||||
|
||||
#### **Session & State Management**
|
||||
- Persistent session state across VS Code restarts
|
||||
- Proper cleanup of resources and event listeners
|
||||
- Better error handling for failed operations
|
||||
- Improved memory management for large conversations
|
||||
|
||||
#### **Cross-Platform Compatibility**
|
||||
- Enhanced WSL support with proper path conversion
|
||||
- Windows-specific improvements and fixes
|
||||
- Better handling of different operating system environments
|
||||
- Improved subprocess management and cleanup
|
||||
|
||||
#### **Performance Optimizations**
|
||||
- Reduced context usage with more efficient tool operations
|
||||
- Better file search and workspace integration
|
||||
- Optimized message handling and UI updates
|
||||
- Improved extension activation and initialization times
|
||||
|
||||
#### **Developer Experience**
|
||||
- Better error messages and debugging information
|
||||
- Improved extension logging and troubleshooting
|
||||
- Enhanced development workflow support
|
||||
- Better TypeScript integration and type safety
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
- Fixed multiple permission prompts being sent simultaneously
|
||||
- Resolved panel management issues with multiple webviews
|
||||
- Fixed expand/collapse functionality for long content
|
||||
- Corrected Unix timestamp parsing for usage limit messages
|
||||
- Fixed WSL integration on Windows systems
|
||||
- Resolved markdown parsing issues with underscores in code
|
||||
- Fixed copy-paste functionality for images and code blocks
|
||||
- Corrected file path handling across different platforms
|
||||
|
||||
### 🔄 Breaking Changes
|
||||
- Permission system now requires explicit approval for tool execution (unless YOLO mode is enabled)
|
||||
- Image files are now stored in `.claude/claude-code-chat-images/` instead of root directory
|
||||
- MCP configuration moved to extension storage instead of global config
|
||||
|
||||
### 📚 Documentation & Community
|
||||
- Updated README with comprehensive feature documentation
|
||||
- Fixed GitHub issues link in repository
|
||||
- Enhanced examples and usage guides
|
||||
- Better onboarding experience for new users
|
||||
|
||||
## [0.1.3] - 2025-06-24
|
||||
|
||||
### 🚀 Features Added
|
||||
|
||||
#### **MultiEdit and Edit Tool Diff Display**
|
||||
- Added comprehensive diff visualization for MultiEdit tool operations
|
||||
- Shows file path with click-to-open functionality
|
||||
- Displays multiple edits with numbered labels (Edit #1, Edit #2, etc.)
|
||||
- Smart truncation handling - shows complete edits within line limits
|
||||
- Expandable interface with "Show X more edits" button
|
||||
- Visual separators between individual edits
|
||||
- Consistent styling with existing Edit tool diff display
|
||||
|
||||
#### **Enhanced Tool Result Management**
|
||||
- Added MultiEdit to hidden tool results list for cleaner interface
|
||||
- Tool results for Read, Edit, TodoWrite, and MultiEdit now show loading states instead of uninteresting success messages
|
||||
- Improved user experience by hiding redundant "Tool executed successfully" messages
|
||||
|
||||
### 🎨 UI/UX Improvements
|
||||
|
||||
#### **Thinking Intensity Modal Enhancement**
|
||||
- Fixed bug where thinking mode toggle text changed before user confirmation
|
||||
- Toggle text now only updates when user clicks "Confirm" button
|
||||
- Preview selection highlighting still works during option exploration
|
||||
- Improved user experience with proper confirmation workflow
|
||||
|
||||
#### **Consistent Message Spacing**
|
||||
- Standardized spacing between tool messages and user/Claude messages
|
||||
- Updated tool input padding from 12px to 8px to match message spacing
|
||||
- Unified visual consistency across all message types
|
||||
|
||||
#### **Refined Visual Design**
|
||||
- Changed MultiEdit edit number labels from purple to subtle professional styling
|
||||
- Used VS Code theme colors for better integration
|
||||
- Improved overall visual cohesion with more sober color palette
|
||||
|
||||
### 🔧 Technical Improvements
|
||||
- Enhanced tool message formatting infrastructure
|
||||
- Improved diff rendering performance for multiple edits
|
||||
- Better error handling for malformed MultiEdit tool inputs
|
||||
- Optimized truncation logic for complex multi-edit operations
|
||||
|
||||
## [0.1.2] - 2025-06-20
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
- Fixed markdown parsing bug where underscores in code identifiers (like "protein_id") were incorrectly converted to italic formatting
|
||||
- Updated regex pattern to only apply italic formatting when underscores are surrounded by whitespace or at string boundaries
|
||||
- Preserved proper formatting for code snippets and technical identifiers
|
||||
- Always show New Chat button
|
||||
|
||||
## [0.1.0] - 2025-06-20
|
||||
|
||||
### 🚀 Major Features Added
|
||||
|
||||
97
README.md
97
README.md
@@ -16,14 +16,14 @@ Ditch the command line and experience Claude Code like never before. This extens
|
||||
|
||||
🖥️ **No Terminal Required** - Beautiful chat interface replaces command-line interactions
|
||||
⏪ **Restore Checkpoints** - Undo changes and restore code to any previous state
|
||||
🔌 **MCP, Skills & Plugins** - Browse, search, and install from curated marketplaces
|
||||
💾 **Conversation History** - Automatic conversation history and session management
|
||||
⚡ **Instant Access** - Claude Code integrated directly into VS Code
|
||||
🎨 **VS Code Native** - Seamlessly matches your editor's theme and design
|
||||
📁 **Smart File Context** - Reference any file with simple @ mentions
|
||||
🛑 **Full Control** - Start, stop, and manage AI processes with ease
|
||||
🤖 **Model Selection** - Choose between Opus, Sonnet, or Default based on your needs
|
||||
🎨 **VS Code & Cursor** - Works in VS Code, Cursor, and other compatible editors
|
||||
🧠 **Plan and Ultrathink modes** - Plan First and Ultrathink modes
|
||||
⚡ **Smart Context** - Reference files, paste images, and create custom commands
|
||||
🐧 **Windows/WSL Support** - Full native Windows and WSL support
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
---
|
||||
@@ -34,8 +34,9 @@ Ditch the command line and experience Claude Code like never before. This extens
|
||||
- No terminal required - everything through the UI
|
||||
- Real-time streaming responses with typing indicators
|
||||
- One-click message copying with visual feedback
|
||||
- Rich markdown support for code blocks and formatting
|
||||
- Enhanced markdown support with syntax highlighting
|
||||
- Auto-resizing input that grows with your content
|
||||
- Copy-to-clipboard for code blocks
|
||||
|
||||
### ⏪ **Checkpoint & Session Management**
|
||||
- **Restore Checkpoints** - Instantly undo changes and restore to any previous state
|
||||
@@ -45,35 +46,71 @@ Ditch the command line and experience Claude Code like never before. This extens
|
||||
- Real-time cost and token tracking
|
||||
- Session statistics and performance metrics
|
||||
|
||||
### 📝 **Inline Diff Viewer**
|
||||
- **Full Diff Display** - See complete file changes directly in Edit, MultiEdit, and Write messages
|
||||
- **Open in VS Code Diff** - One-click button to open VS Code's native side-by-side diff editor
|
||||
- **Smart Truncation** - Long diffs are truncated with an expand button for better readability
|
||||
- **Syntax Highlighting** - Proper code highlighting in diff views
|
||||
- **Visual Change Indicators** - Clear green/red highlighting for additions and deletions
|
||||
|
||||
### 🔌 **MCP, Skills & Plugins Marketplace** ⭐ **NEW IN V2.0**
|
||||
- **MCP Servers** - Browse 30+ featured servers (GitHub, Slack, Stripe, Notion, etc.) with dual registry search
|
||||
- **Skills** - Browse and install curated skills from skills.sh with project or global scope
|
||||
- **Plugins** - Browse and install plugins to extend Claude Code
|
||||
- **Smart Search** - Search across add-mcp curated and official Anthropic registries with relevance ranking
|
||||
- **Project or Global Install** - Install MCP servers to `.mcp.json` or `~/.claude.json`, skills to `.claude/skills/`
|
||||
- **One-Click Install** - Pre-filled configuration with env vars, headers, and OAuth authentication
|
||||
|
||||
### 🖼️ **Image Preview & Attachments** ⭐ **NEW IN V2.0**
|
||||
- **Paste Images** - Paste images with thumbnail preview before sending
|
||||
- **File Picker** - Select images through VS Code's native file picker
|
||||
- **Preview Strip** - See attached images above the text box with remove buttons
|
||||
- **Inline Detection** - Image paths in messages are automatically detected and sent as base64
|
||||
- **Multiple Images** - Attach multiple images to a single message
|
||||
|
||||
### 🔒 **Advanced Permissions System**
|
||||
- **Interactive Permission Dialogs** - Detailed tool information with command previews
|
||||
- **Always Allow Functionality** - Smart command pattern matching for common tools (npm, git, docker)
|
||||
- **YOLO Mode** - Skip all permission checks for power users
|
||||
- **Workspace Permissions** - Granular control over what tools can execute
|
||||
- **Real-time Permission Management** - Add/remove permissions through intuitive UI
|
||||
|
||||
### 📱 **Sidebar Integration**
|
||||
- **Native VS Code Sidebar** - Full chat functionality in the sidebar panel
|
||||
- **Smart Panel Management** - Automatic switching between main and sidebar views
|
||||
- **Persistent Sessions** - State maintained across panel switches
|
||||
- **Activity Bar Integration** - Quick access from VS Code's activity bar
|
||||
|
||||
### 📁 **Smart File Integration**
|
||||
- Type `@` to instantly search and reference workspace files
|
||||
- Image attachments via file browser
|
||||
- Image attachments via file browser and copy-paste screenshots
|
||||
- Lightning-fast file search across your entire project
|
||||
- Seamless context preservation for multi-file discussions
|
||||
|
||||
### 🛠️ **Tool Management**
|
||||
- Visual dashboard showing all available Claude Code tools
|
||||
- Real-time tool execution with formatted results
|
||||
- Process control - start, stop, and monitor operations
|
||||
- Inline stop button replaces send during processing
|
||||
- Smart permission system for secure tool execution
|
||||
|
||||
### 🎨 **VS Code Integration**
|
||||
- Native theming that matches your editor
|
||||
- Status bar integration with connection status
|
||||
- Status bar with support button
|
||||
- Activity bar panel for quick access
|
||||
- Responsive design for any screen size
|
||||
|
||||
### 🤖 **Model Selection**
|
||||
- **Quick Buttons** - GPT, Gemini, MiniMax, Kimi, GLM, DeepSeek above the text box
|
||||
- **Opus** - Most capable model for complex tasks requiring deep reasoning
|
||||
- **Sonnet** - Balanced model offering great performance for most use cases
|
||||
- **Default** - Uses your configured model setting
|
||||
- Model preference persists across sessions and is saved automatically
|
||||
- Easy switching via dropdown selector in the chat interface
|
||||
- Visual confirmation when switching between models
|
||||
- One-click model configuration through integrated terminal
|
||||
- **150+ OpenCredits Models** - Browse and switch to any available model
|
||||
- Model preference persists across sessions
|
||||
- Provider choice (OpenCredits vs Anthropic) for Claude models
|
||||
|
||||
### ⚡ **Slash Commands Integration**
|
||||
- **Slash Commands Modal** - Type "/" to access all Claude Code commands instantly
|
||||
- **19+ Built-in Commands** - /cost, /status, /config, /help, /memory, /review, and more
|
||||
- **23+ Built-in Commands** - /agents, /cost, /config, /memory, /review, and more
|
||||
- **Custom Command Support** - Execute any Claude Code command with session context
|
||||
- **Session-Aware Execution** - All commands run with current conversation context
|
||||
- **Terminal Integration** - Commands open directly in VS Code terminal with WSL support
|
||||
@@ -85,14 +122,20 @@ Ditch the command line and experience Claude Code like never before. This extens
|
||||
- **Intelligent Prompting** - Different prompts based on selected thinking intensity
|
||||
- **Token Awareness** - Higher thinking levels consume more tokens but provide deeper reasoning
|
||||
|
||||
### 💬 **Support & Feedback** ⭐ **NEW IN V2.0**
|
||||
- **In-App Support** - Click "Support" in the status bar to send feedback
|
||||
- **Bug Reports & Feature Requests** - Submit directly from the extension
|
||||
- **Optional Email** - Include your email for follow-up
|
||||
|
||||
---
|
||||
|
||||
## 🚀 **Getting Started**
|
||||
|
||||
### Prerequisites
|
||||
- **VS Code 1.80+** - Latest version recommended
|
||||
- **VS Code 1.80+** or **Cursor** - Latest version recommended
|
||||
- **Claude Code CLI** - [Install from Anthropic](https://claude.ai/code)
|
||||
- **Active Claude API or subscription** - API or Pro/Max plan
|
||||
- **Node.js 18+** - Required for installation
|
||||
|
||||
### Installation
|
||||
|
||||
@@ -214,31 +257,40 @@ Example configuration in `settings.json`:
|
||||
- Type `@` followed by your search term to quickly reference files
|
||||
- Use `@src/` to narrow down to specific directories
|
||||
- Reference multiple files in one message for cross-file analysis
|
||||
- Paste images directly with preview thumbnails before sending
|
||||
- Paste screenshots with Ctrl+V for instant visual communication
|
||||
|
||||
### ⚡ **Productivity Boosters**
|
||||
- **Creates checkpoints automatically** before changes for safe experimentation
|
||||
- **Restore instantly** if changes don't work out as expected
|
||||
- Use the stop button to cancel long-running operations
|
||||
- Permission system prevents accidental tool execution
|
||||
- YOLO mode for power users who want speed over safety
|
||||
- Inline stop button to cancel long-running operations
|
||||
- Copy message contents to reuse Claude's responses
|
||||
- Open history panel to reference previous conversations
|
||||
- Sidebar integration for multi-panel workflow
|
||||
- **Plan mode** and **Ultrathink** toggles above the text box
|
||||
|
||||
### 🎨 **Interface Customization**
|
||||
- The UI automatically adapts to your VS Code theme
|
||||
- Messages are color-coded: Green for you, Blue for Claude
|
||||
- Hover over messages to reveal the copy button
|
||||
- Enhanced code block rendering with syntax highlighting
|
||||
- Copy-to-clipboard functionality for code blocks
|
||||
|
||||
---
|
||||
|
||||
## 🔧 **Advanced Features**
|
||||
|
||||
### 🛠️ **Tool Integration**
|
||||
Claude Code Chat provides full access to all Claude Code tools:
|
||||
- **Bash** - Execute shell commands
|
||||
Claude Code Chat provides secure access to all Claude Code tools:
|
||||
- **Bash** - Execute shell commands with permission controls
|
||||
- **File Operations** - Read, write, and edit files
|
||||
- **Search** - Grep and glob pattern matching
|
||||
- **Search** - Grep and glob pattern matching across workspace
|
||||
- **Web** - Fetch and search web content
|
||||
- **Multi-edit** - Batch file modifications
|
||||
- **While in Beta, all tools are enabled by default, use at your own risk!**
|
||||
- **MCP Servers** - Extend functionality with Model Context Protocol servers
|
||||
- **Permissions System** - Granular control over tool execution for security
|
||||
|
||||
### 📊 **Analytics & Monitoring**
|
||||
- **Real-time cost tracking** - Monitor your API usage
|
||||
@@ -296,7 +348,8 @@ See the [LICENSE](LICENSE) file for details.
|
||||
|
||||
Need help? We've got you covered:
|
||||
|
||||
- 🐛 **Issues**: [GitHub Issues](https://github.com/your-repo/claude-code-chat/issues)
|
||||
- 💬 **In-App** - Click "Support" in the status bar to send feedback directly
|
||||
- 🐛 **Issues**: [GitHub Issues](https://github.com/andrepimenta/claude-code-chat/issues)
|
||||
|
||||
---
|
||||
|
||||
|
||||
21
backup.sh
Executable file
21
backup.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Backup script for src folder
|
||||
|
||||
# Get the directory where the script is located
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Create backup directory if it doesn't exist
|
||||
BACKUP_DIR="$SCRIPT_DIR/backup-files"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
# Generate timestamp
|
||||
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
|
||||
|
||||
# Create backup filename
|
||||
BACKUP_NAME="src-backup-$TIMESTAMP"
|
||||
|
||||
# Copy src folder to backup
|
||||
cp -r "$SCRIPT_DIR/src" "$BACKUP_DIR/$BACKUP_NAME"
|
||||
|
||||
echo "Backup created: $BACKUP_DIR/$BACKUP_NAME"
|
||||
71
build/open-vsix/build.sh
Executable file
71
build/open-vsix/build.sh
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Build script for Open VSIX version
|
||||
# This applies Open VSIX-specific changes, builds the package, then reverts
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VERSION="2.0.6"
|
||||
OUTPUT_NAME="vsix-claude-code-chat-${VERSION}.vsix"
|
||||
|
||||
echo "Building Open VSIX version ${VERSION}..."
|
||||
|
||||
# Backup original files to build folder
|
||||
cp package.json "${SCRIPT_DIR}/package.json.backup"
|
||||
cp src/extension.ts "${SCRIPT_DIR}/extension.ts.backup"
|
||||
|
||||
# Backup original icon.png if it exists
|
||||
if [ -f "icon.png" ]; then
|
||||
mv icon.png "${SCRIPT_DIR}/icon.png.backup"
|
||||
fi
|
||||
|
||||
# Copy Open VSIX icon
|
||||
cp "${SCRIPT_DIR}/icon.png" icon.png
|
||||
echo "Copied Open VSIX icon"
|
||||
|
||||
# Temporarily remove icon-bubble.png (not needed for Open VSIX)
|
||||
if [ -f "icon-bubble.png" ]; then
|
||||
mv icon-bubble.png "${SCRIPT_DIR}/icon-bubble.png.backup"
|
||||
fi
|
||||
|
||||
# Apply Open VSIX changes to package.json
|
||||
sed -i.bak 's/"displayName": "Chat for Claude Code"/"displayName": "Claude Code Chat"/' package.json
|
||||
sed -i.bak 's/"icon": "icon-bubble.png"/"icon": "icon.png"/g' package.json
|
||||
rm -f package.json.bak
|
||||
|
||||
# Apply Open VSIX changes to extension.ts
|
||||
sed -i.bak "s/icon-bubble.png/icon.png/g" src/extension.ts
|
||||
rm -f src/extension.ts.bak
|
||||
|
||||
echo "Applied Open VSIX changes to package.json and extension.ts"
|
||||
|
||||
# Compile TypeScript
|
||||
echo "Compiling TypeScript..."
|
||||
npm run compile
|
||||
|
||||
# Build the VSIX
|
||||
echo "Building VSIX package..."
|
||||
vsce package --out "${OUTPUT_NAME}"
|
||||
|
||||
# Restore original files from build folder
|
||||
mv "${SCRIPT_DIR}/package.json.backup" package.json
|
||||
mv "${SCRIPT_DIR}/extension.ts.backup" src/extension.ts
|
||||
|
||||
# Restore original icon
|
||||
rm -f icon.png
|
||||
if [ -f "${SCRIPT_DIR}/icon.png.backup" ]; then
|
||||
mv "${SCRIPT_DIR}/icon.png.backup" icon.png
|
||||
fi
|
||||
|
||||
# Restore icon-bubble.png
|
||||
if [ -f "${SCRIPT_DIR}/icon-bubble.png.backup" ]; then
|
||||
mv "${SCRIPT_DIR}/icon-bubble.png.backup" icon-bubble.png
|
||||
fi
|
||||
|
||||
# Recompile with original extension.ts
|
||||
echo "Recompiling with original files..."
|
||||
npm run compile
|
||||
|
||||
echo "Restored original files"
|
||||
echo "Built: ${OUTPUT_NAME}"
|
||||
BIN
build/open-vsix/icon.png
Normal file
BIN
build/open-vsix/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 689 KiB |
13954
claude-code-chat-permissions-mcp/mcp-permissions.js
Normal file
13954
claude-code-chat-permissions-mcp/mcp-permissions.js
Normal file
File diff suppressed because one or more lines are too long
212
claude-code-chat-permissions-mcp/mcp-permissions.ts
Normal file
212
claude-code-chat-permissions-mcp/mcp-permissions.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { z } from "zod";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
const server = new McpServer({
|
||||
name: "Claude Code Permissions MCP Server",
|
||||
version: "0.0.1",
|
||||
});
|
||||
|
||||
// Get permissions directory from environment
|
||||
const PERMISSIONS_PATH = process.env.CLAUDE_PERMISSIONS_PATH;
|
||||
if (!PERMISSIONS_PATH) {
|
||||
console.error("CLAUDE_PERMISSIONS_PATH environment variable not set");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
interface WorkspacePermissions {
|
||||
alwaysAllow: {
|
||||
[toolName: string]: boolean | string[]; // true for all, or array of allowed commands/patterns
|
||||
};
|
||||
}
|
||||
|
||||
function getWorkspacePermissionsPath(): string | null {
|
||||
if (!PERMISSIONS_PATH) return null;
|
||||
return path.join(PERMISSIONS_PATH, 'permissions.json');
|
||||
}
|
||||
|
||||
function loadWorkspacePermissions(): WorkspacePermissions {
|
||||
const permissionsPath = getWorkspacePermissionsPath();
|
||||
if (!permissionsPath || !fs.existsSync(permissionsPath)) {
|
||||
return { alwaysAllow: {} };
|
||||
}
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(permissionsPath, 'utf8');
|
||||
return JSON.parse(content);
|
||||
} catch (error) {
|
||||
console.error(`Error loading workspace permissions: ${error}`);
|
||||
return { alwaysAllow: {} };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function isAlwaysAllowed(toolName: string, input: any): boolean {
|
||||
const permissions = loadWorkspacePermissions();
|
||||
const toolPermission = permissions.alwaysAllow[toolName];
|
||||
|
||||
if (!toolPermission) return false;
|
||||
|
||||
// If it's true, always allow
|
||||
if (toolPermission === true) return true;
|
||||
|
||||
// If it's an array, check for specific commands (mainly for Bash)
|
||||
if (Array.isArray(toolPermission)) {
|
||||
if (toolName === 'Bash' && input.command) {
|
||||
const command = input.command.trim();
|
||||
return toolPermission.some(allowedCmd => {
|
||||
// Support exact match or pattern matching
|
||||
if (allowedCmd.includes('*')) {
|
||||
// Handle patterns like "npm i *" to match both "npm i" and "npm i something"
|
||||
const baseCommand = allowedCmd.replace(' *', '');
|
||||
if (command === baseCommand) {
|
||||
return true; // Exact match for base command
|
||||
}
|
||||
// Pattern match for command with arguments
|
||||
const pattern = allowedCmd.replace(/\*/g, '.*');
|
||||
return new RegExp(`^${pattern}$`).test(command);
|
||||
}
|
||||
return command.startsWith(allowedCmd);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function generateRequestId(): string {
|
||||
return `req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
||||
}
|
||||
|
||||
async function requestPermission(tool_name: string, input: any): Promise<{approved: boolean, reason?: string}> {
|
||||
if (!PERMISSIONS_PATH) {
|
||||
console.error("Permissions path not available");
|
||||
return { approved: false, reason: "Permissions path not configured" };
|
||||
}
|
||||
|
||||
// Check if this tool/command is always allowed for this workspace
|
||||
if (isAlwaysAllowed(tool_name, input)) {
|
||||
console.error(`Tool ${tool_name} is always allowed for this workspace`);
|
||||
return { approved: true };
|
||||
}
|
||||
|
||||
const requestId = generateRequestId();
|
||||
const requestFile = path.join(PERMISSIONS_PATH, `${requestId}.request`);
|
||||
const responseFile = path.join(PERMISSIONS_PATH, `${requestId}.response`);
|
||||
|
||||
// Write request file
|
||||
const request = {
|
||||
id: requestId,
|
||||
tool: tool_name,
|
||||
input: input,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
try {
|
||||
fs.writeFileSync(requestFile, JSON.stringify(request, null, 2));
|
||||
|
||||
// Use fs.watch to wait for response file
|
||||
return new Promise<{approved: boolean, reason?: string}>((resolve) => {
|
||||
const timeout = setTimeout(() => {
|
||||
watcher.close();
|
||||
// Clean up request file on timeout
|
||||
if (fs.existsSync(requestFile)) {
|
||||
fs.unlinkSync(requestFile);
|
||||
}
|
||||
console.error(`Permission request ${requestId} timed out`);
|
||||
resolve({ approved: false, reason: "Permission request timed out" });
|
||||
}, 3600000); // 1 hour timeout
|
||||
|
||||
const watcher = fs.watch(PERMISSIONS_PATH, (eventType, filename) => {
|
||||
if (eventType === 'rename' && filename === path.basename(responseFile)) {
|
||||
// Check if file exists (rename event can be for creation or deletion)
|
||||
if (fs.existsSync(responseFile)) {
|
||||
try {
|
||||
const responseContent = fs.readFileSync(responseFile, 'utf8');
|
||||
const response = JSON.parse(responseContent);
|
||||
|
||||
// Clean up response file
|
||||
fs.unlinkSync(responseFile);
|
||||
|
||||
// Clear timeout and close watcher
|
||||
clearTimeout(timeout);
|
||||
watcher.close();
|
||||
|
||||
resolve({
|
||||
approved: response.approved,
|
||||
reason: response.approved ? undefined : "User rejected the request"
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error reading response file: ${error}`);
|
||||
// Continue watching in case of read error
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle watcher errors
|
||||
watcher.on('error', (error) => {
|
||||
console.error(`File watcher error: ${error}`);
|
||||
clearTimeout(timeout);
|
||||
watcher.close();
|
||||
resolve({ approved: false, reason: "File watcher error" });
|
||||
});
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error requesting permission: ${error}`);
|
||||
return { approved: false, reason: `Error processing permission request: ${error}` };
|
||||
}
|
||||
}
|
||||
|
||||
server.tool(
|
||||
"approval_prompt",
|
||||
'Request user permission to execute a tool via VS Code dialog',
|
||||
{
|
||||
tool_name: z.string().describe("The name of the tool requesting permission"),
|
||||
input: z.object({}).passthrough().describe("The input for the tool"),
|
||||
tool_use_id: z.string().optional().describe("The unique tool use request ID"),
|
||||
},
|
||||
async ({ tool_name, input }) => {
|
||||
console.error(`Requesting permission for tool: ${tool_name}`);
|
||||
|
||||
const permissionResult = await requestPermission(tool_name, input);
|
||||
|
||||
const behavior = permissionResult.approved ? "allow" : "deny";
|
||||
console.error(`Permission ${behavior}ed for tool: ${tool_name}`);
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: behavior === "allow" ?
|
||||
JSON.stringify({
|
||||
behavior: behavior,
|
||||
updatedInput: input,
|
||||
})
|
||||
:
|
||||
JSON.stringify({
|
||||
behavior: behavior,
|
||||
message: permissionResult.reason || "Permission denied",
|
||||
})
|
||||
,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
async function main() {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error(`Permissions MCP Server running on stdio`);
|
||||
console.error(`Using permissions directory: ${PERMISSIONS_PATH}`);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error("Fatal error in main():", error);
|
||||
process.exit(1);
|
||||
});
|
||||
1086
claude-code-chat-permissions-mcp/package-lock.json
generated
Normal file
1086
claude-code-chat-permissions-mcp/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
claude-code-chat-permissions-mcp/package.json
Normal file
21
claude-code-chat-permissions-mcp/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "claude-code-chat-permissions-mcp",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/mcp-permissions.js",
|
||||
"scripts": {
|
||||
"start": "tsc && node dist/mcp-permissions.js",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.13",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.15.1",
|
||||
"zod": "^3.25.76"
|
||||
}
|
||||
}
|
||||
11
claude-code-chat-permissions-mcp/tsconfig.json
Normal file
11
claude-code-chat-permissions-mcp/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"target": "es6",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"outDir": "dist"
|
||||
},
|
||||
"lib": ["es2015"]
|
||||
}
|
||||
BIN
icon-bubble.png
Normal file
BIN
icon-bubble.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 469 KiB |
BIN
icon.png
BIN
icon.png
Binary file not shown.
|
Before Width: | Height: | Size: 689 KiB After Width: | Height: | Size: 689 KiB |
13954
mcp-permissions.js
Normal file
13954
mcp-permissions.js
Normal file
File diff suppressed because one or more lines are too long
14
package-lock.json
generated
14
package-lock.json
generated
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "claude-code-chat",
|
||||
"version": "0.0.8",
|
||||
"version": "2.0.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "claude-code-chat",
|
||||
"version": "0.0.8",
|
||||
"version": "2.0.6",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "20.x",
|
||||
"@types/vscode": "^1.95.0",
|
||||
"@types/vscode": "^1.94.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.31.1",
|
||||
"@typescript-eslint/parser": "^8.31.1",
|
||||
"@vscode/test-cli": "^0.0.10",
|
||||
@@ -21,7 +21,7 @@
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.95.0"
|
||||
"vscode": "^1.94.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azu/format-text": {
|
||||
@@ -978,9 +978,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/vscode": {
|
||||
"version": "1.100.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.100.0.tgz",
|
||||
"integrity": "sha512-4uNyvzHoraXEeCamR3+fzcBlh7Afs4Ifjs4epINyUX/jvdk0uzLnwiDY35UKDKnkCHP5Nu3dljl2H8lR6s+rQw==",
|
||||
"version": "1.101.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.101.0.tgz",
|
||||
"integrity": "sha512-ZWf0IWa+NGegdW3iU42AcDTFHWW7fApLdkdnBqwYEtHVIBGbTu0ZNQKP/kX3Ds/uMJXIMQNAojHR4vexCEEz5Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
||||
51
package.json
51
package.json
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "claude-code-chat",
|
||||
"displayName": "Claude Code Chat",
|
||||
"displayName": "Chat for Claude Code",
|
||||
"description": "Beautiful Claude Code Chat Interface for VS Code",
|
||||
"version": "0.1.0",
|
||||
"version": "2.0.6",
|
||||
"publisher": "AndrePimenta",
|
||||
"author": "Andre Pimenta",
|
||||
"repository": {
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"engines": {
|
||||
"vscode": "^1.95.0"
|
||||
"vscode": "^1.94.0"
|
||||
},
|
||||
"categories": [
|
||||
"Other",
|
||||
@@ -56,7 +56,7 @@
|
||||
"command": "claude-code-chat.openChat",
|
||||
"title": "Open Claude Code Chat",
|
||||
"category": "Claude Code Chat",
|
||||
"icon": "icon.png"
|
||||
"icon": "icon-bubble.png"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
@@ -130,9 +130,10 @@
|
||||
"claude-code-chat": [
|
||||
{
|
||||
"id": "claude-code-chat.chat",
|
||||
"type": "webview",
|
||||
"name": "Claude Code Chat",
|
||||
"when": "true",
|
||||
"icon": "icon.png",
|
||||
"icon": "icon-bubble.png",
|
||||
"contextualTitle": "Claude Code Chat"
|
||||
}
|
||||
]
|
||||
@@ -142,7 +143,7 @@
|
||||
{
|
||||
"id": "claude-code-chat",
|
||||
"title": "Claude Code Chat",
|
||||
"icon": "icon.png"
|
||||
"icon": "icon-bubble.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -161,8 +162,8 @@
|
||||
},
|
||||
"claudeCodeChat.wsl.nodePath": {
|
||||
"type": "string",
|
||||
"default": "/usr/bin/node",
|
||||
"description": "Path to Node.js in the WSL distribution"
|
||||
"default": "",
|
||||
"description": "Optional path to Node.js in the WSL distribution. Only needed if Claude was installed via npm. Recent Claude installs ship as a native executable and don't require Node."
|
||||
},
|
||||
"claudeCodeChat.wsl.claudePath": {
|
||||
"type": "string",
|
||||
@@ -171,9 +172,39 @@
|
||||
},
|
||||
"claudeCodeChat.thinking.intensity": {
|
||||
"type": "string",
|
||||
"enum": ["think", "think-hard", "think-harder", "ultrathink"],
|
||||
"enum": [
|
||||
"think",
|
||||
"think-hard",
|
||||
"think-harder",
|
||||
"ultrathink"
|
||||
],
|
||||
"default": "think",
|
||||
"description": "Thinking mode intensity level. Higher levels provide more detailed reasoning but consume more tokens."
|
||||
},
|
||||
"claudeCodeChat.permissions.yoloMode": {
|
||||
"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.executable.path": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Custom path to the Claude Code executable. Leave empty to use the default 'claude' command."
|
||||
},
|
||||
"claudeCodeChat.environment.variables": {
|
||||
"type": "object",
|
||||
"default": {},
|
||||
"description": "Custom environment variables to pass to Claude Code. Example: {\"ANTHROPIC_API_KEY\": \"sk-...\"}"
|
||||
},
|
||||
"claudeCodeChat.environment.disabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "When enabled, custom environment variables are not passed to Claude Code."
|
||||
},
|
||||
"claudeCodeChat.router.enabled": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Enable the local router to convert OpenAI format to Anthropic format. Required for providers that use OpenAI-compatible APIs."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,7 +220,7 @@
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "20.x",
|
||||
"@types/vscode": "^1.95.0",
|
||||
"@types/vscode": "^1.94.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.31.1",
|
||||
"@typescript-eslint/parser": "^8.31.1",
|
||||
"@vscode/test-cli": "^0.0.10",
|
||||
|
||||
3499
src/extension.ts
3499
src/extension.ts
File diff suppressed because it is too large
Load Diff
148
src/model-updater.ts
Normal file
148
src/model-updater.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
interface ApiModel {
|
||||
id: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
pricing?: { prompt: number; completion: number; currency?: string; unit?: string };
|
||||
context_length?: number;
|
||||
max_output_tokens?: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface BundledModel {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
provider: string;
|
||||
quickLabel?: string;
|
||||
context_length?: number;
|
||||
max_output_tokens?: number;
|
||||
tierModels?: { sonnet: string; opus: string; haiku: string };
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface ProviderResolver {
|
||||
main: RegExp;
|
||||
opus?: RegExp;
|
||||
haiku?: RegExp;
|
||||
}
|
||||
|
||||
function parseVersion(ver: string): number[] {
|
||||
return ver.split('.').map(Number);
|
||||
}
|
||||
|
||||
function compareVersions(a: string, b: string): number {
|
||||
const va = parseVersion(a);
|
||||
const vb = parseVersion(b);
|
||||
for (let i = 0; i < Math.max(va.length, vb.length); i++) {
|
||||
const na = va[i] || 0;
|
||||
const nb = vb[i] || 0;
|
||||
if (na !== nb) { return na - nb; }
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function findHighestMatch(apiModels: ApiModel[], regex: RegExp): ApiModel | null {
|
||||
let best: ApiModel | null = null;
|
||||
let bestVer: string | null = null;
|
||||
for (const m of apiModels) {
|
||||
const match = regex.exec(m.id);
|
||||
if (match) {
|
||||
const ver = match[1] || '0';
|
||||
if (!bestVer || compareVersions(ver, bestVer) > 0) {
|
||||
bestVer = ver;
|
||||
best = m;
|
||||
}
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
const providerResolvers: Record<string, ProviderResolver> = {
|
||||
'zai/glm-': {
|
||||
main: /^zai\/glm-(\d+(?:\.\d+)?)$/,
|
||||
haiku: /^zai\/GLM-([\d.]+)-(?:Air|Flash)$/i
|
||||
},
|
||||
'openai/gpt-': {
|
||||
main: /^openai\/gpt-([\d.]+)-codex$/,
|
||||
haiku: /^openai\/gpt-([\d.]+)-codex-mini$/
|
||||
},
|
||||
'gemini-': {
|
||||
main: /^(?:google\/)?gemini-([\d.]+)-pro-preview$/,
|
||||
opus: /^(?:google\/)?gemini-([\d.]+)-pro-preview-thinking$/,
|
||||
haiku: /^(?:google\/)?gemini-([\d.]+)-flash(?:-preview)?$/
|
||||
},
|
||||
'deepseek/deepseek-': {
|
||||
main: /^deepseek\/deepseek-v([\d.]+)[-:]thinking$/
|
||||
},
|
||||
'minimax/minimax-': {
|
||||
main: /^minimax\/minimax-m([\d.]+)$/
|
||||
},
|
||||
'moonshotai/kimi-': {
|
||||
main: /^moonshotai\/kimi-k([\d.]+)$/,
|
||||
haiku: /^moonshotai\/kimi-k([\d.]+)-turbo$/
|
||||
}
|
||||
};
|
||||
|
||||
export function resolveLatestModels(apiModels: ApiModel[], bundledModels: BundledModel[]): BundledModel[] {
|
||||
return bundledModels.map(bundled => {
|
||||
const b: BundledModel = JSON.parse(JSON.stringify(bundled));
|
||||
|
||||
let resolver: ProviderResolver | null = null;
|
||||
for (const prefix of Object.keys(providerResolvers)) {
|
||||
if (b.id.toLowerCase().startsWith(prefix)) {
|
||||
resolver = providerResolvers[prefix];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!resolver) { return b; }
|
||||
|
||||
// Resolve main (sonnet-tier) model
|
||||
const mainMatch = findHighestMatch(apiModels, resolver.main);
|
||||
if (mainMatch) {
|
||||
b.id = mainMatch.id;
|
||||
b.name = mainMatch.name || b.name;
|
||||
b.description = mainMatch.description || b.description;
|
||||
b.context_length = mainMatch.context_length || b.context_length;
|
||||
b.max_output_tokens = mainMatch.max_output_tokens || b.max_output_tokens;
|
||||
if (b.tierModels) {
|
||||
b.tierModels.sonnet = mainMatch.id;
|
||||
if (!resolver.opus) {
|
||||
b.tierModels.opus = mainMatch.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve opus-tier model (e.g. Gemini thinking variant)
|
||||
if (resolver.opus && b.tierModels) {
|
||||
const opusMatch = findHighestMatch(apiModels, resolver.opus);
|
||||
if (opusMatch) {
|
||||
b.tierModels.opus = opusMatch.id;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve haiku-tier model
|
||||
if (resolver.haiku && b.tierModels) {
|
||||
const haikuMatch = findHighestMatch(apiModels, resolver.haiku);
|
||||
if (haikuMatch) {
|
||||
b.tierModels.haiku = haikuMatch.id;
|
||||
}
|
||||
}
|
||||
|
||||
return b;
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchAndResolveModels(bundledModels: BundledModel[], apiBaseUrl: string = 'https://ccc.api.opencredits.ai'): Promise<BundledModel[] | null> {
|
||||
try {
|
||||
const response = await fetch(apiBaseUrl + '/v1/models');
|
||||
const data: any = await response.json();
|
||||
const apiModels: ApiModel[] = data.data || data;
|
||||
if (!Array.isArray(apiModels) || apiModels.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return resolveLatestModels(apiModels, bundledModels);
|
||||
} catch (e) {
|
||||
console.log('Auto-update models failed:', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
153
src/plugins-script.ts
Normal file
153
src/plugins-script.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
const getPluginsScript = () => `
|
||||
// ─── Plugins ───
|
||||
var topPlugins = (window.__topPlugins || []);
|
||||
var pluginsDisplayedList = null;
|
||||
|
||||
function formatPluginName(name) {
|
||||
return name.replace(/-/g, ' ').replace(/\\b\\w/g, function(c) { return c.toUpperCase(); });
|
||||
}
|
||||
|
||||
function showPluginsModal() {
|
||||
sendStats('Plugins modal opened');
|
||||
document.getElementById('pluginsModal').style.display = 'flex';
|
||||
loadInstalledPlugins();
|
||||
renderAvailablePlugins(topPlugins);
|
||||
}
|
||||
|
||||
function hidePluginsModal() {
|
||||
document.getElementById('pluginsModal').style.display = 'none';
|
||||
}
|
||||
|
||||
function loadInstalledPlugins() {
|
||||
vscode.postMessage({ type: 'loadPlugins' });
|
||||
}
|
||||
|
||||
function displayPlugins(data) {
|
||||
var pluginsList = document.getElementById('pluginsList');
|
||||
pluginsList.innerHTML = '';
|
||||
var enabled = data.enabled || {};
|
||||
|
||||
var keys = Object.keys(enabled);
|
||||
if (keys.length === 0) {
|
||||
pluginsList.innerHTML = '<div class="no-servers">' +
|
||||
'<div class="no-servers-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg></div>' +
|
||||
'<div class="no-servers-text">No plugins enabled</div>' +
|
||||
'</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
keys.forEach(function(installId) {
|
||||
var isEnabled = enabled[installId];
|
||||
var name = installId.replace(/@.*$/, '');
|
||||
var displayName = formatPluginName(name);
|
||||
var plugin = topPlugins.find(function(p) { return p.installId === installId; });
|
||||
var desc = plugin ? plugin.description : '';
|
||||
var verified = plugin ? plugin.verified : false;
|
||||
|
||||
var item = document.createElement('div');
|
||||
item.className = 'mcp-server-item';
|
||||
var verifiedHtml = verified ? '<span class="marketplace-item-verified" title="Anthropic verified">✓</span>' : '';
|
||||
var statusHtml = isEnabled ? '<span class="server-type" style="background:rgba(0,122,204,0.2);color:var(--vscode-charts-blue);">enabled</span>' : '<span class="server-type">disabled</span>';
|
||||
item.innerHTML = '<div class="server-info" style="min-width:0;overflow:hidden;">' +
|
||||
'<div class="server-name">' + escapeHtml(displayName) + verifiedHtml + ' ' + statusHtml + '</div>' +
|
||||
(desc ? '<div class="server-config" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">' + escapeHtml(desc) + '</div>' : '') +
|
||||
'</div>' +
|
||||
'<div class="server-actions" style="flex-shrink:0;">' +
|
||||
'<button class="btn outlined server-delete-btn" data-plugin="' + escapeHtml(installId) + '" onclick="removePlugin(this.dataset.plugin)">Remove</button>' +
|
||||
'</div>';
|
||||
pluginsList.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function renderAvailablePlugins(plugins) {
|
||||
var grid = document.getElementById('pluginsGrid');
|
||||
if (!grid) return;
|
||||
if (!plugins || plugins.length === 0) {
|
||||
grid.innerHTML = '<div class="marketplace-loading">No plugins found.</div>';
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
plugins.forEach(function(plugin) {
|
||||
var name = plugin.name || 'Unknown';
|
||||
var displayName = formatPluginName(name);
|
||||
var desc = escapeHtml(plugin.description || 'No description');
|
||||
var verified = plugin.verified;
|
||||
var safeId = escapeHtml(plugin.installId || name).replace(/'/g, ''');
|
||||
|
||||
html += '<div class="marketplace-item" data-plugin-id="' + safeId + '" onclick="showPluginDetail(this.dataset.pluginId)">' +
|
||||
'<div class="marketplace-item-header">' +
|
||||
'<div class="marketplace-item-icon-placeholder">' + escapeHtml(displayName.charAt(0).toUpperCase()) + '</div>' +
|
||||
'<div class="marketplace-item-info">' +
|
||||
'<div class="marketplace-item-name">' + escapeHtml(displayName) + '</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="marketplace-item-desc">' + desc + '</div>' +
|
||||
'</div>';
|
||||
});
|
||||
grid.innerHTML = html;
|
||||
}
|
||||
|
||||
function searchPlugins(query) {
|
||||
if (!query) {
|
||||
renderAvailablePlugins(topPlugins);
|
||||
return;
|
||||
}
|
||||
var q = query.toLowerCase();
|
||||
var filtered = topPlugins.filter(function(p) {
|
||||
return (p.name && p.name.toLowerCase().indexOf(q) >= 0) ||
|
||||
(p.description && p.description.toLowerCase().indexOf(q) >= 0);
|
||||
});
|
||||
renderAvailablePlugins(filtered);
|
||||
}
|
||||
|
||||
function showPluginDetail(installId) {
|
||||
var plugin = topPlugins.find(function(p) { return p.installId === installId; });
|
||||
if (!plugin) return;
|
||||
|
||||
var name = plugin.name || 'Unknown';
|
||||
var displayName = formatPluginName(name);
|
||||
var desc = plugin.description || 'No description available.';
|
||||
var verified = plugin.verified;
|
||||
var verifiedHtml = verified ? '<span class="marketplace-item-verified" title="Anthropic verified">✓ Anthropic verified</span>' : '';
|
||||
|
||||
var grid = document.getElementById('pluginsGrid');
|
||||
pluginsDisplayedList = grid.innerHTML;
|
||||
|
||||
grid.innerHTML = '<div class="marketplace-detail">' +
|
||||
'<button class="marketplace-back-btn" onclick="backToPluginsList()">← Back</button>' +
|
||||
'<div class="marketplace-detail-header">' +
|
||||
'<div class="marketplace-item-icon-placeholder" style="width:40px;height:40px;font-size:18px;">' + escapeHtml(displayName.charAt(0).toUpperCase()) + '</div>' +
|
||||
'<div class="marketplace-detail-header-info">' +
|
||||
'<div class="marketplace-detail-name">' + escapeHtml(displayName) + '</div>' +
|
||||
'<div class="marketplace-detail-header-meta">' + verifiedHtml + '</div>' +
|
||||
'</div>' +
|
||||
'<button class="btn marketplace-install-btn" data-plugin="' + escapeHtml(installId) + '" onclick="installPlugin(this.dataset.plugin)">Enable</button>' +
|
||||
'</div>' +
|
||||
'<div class="marketplace-detail-desc">' + escapeHtml(desc) + '</div>' +
|
||||
'<div class="marketplace-detail-row"><a href="https://github.com/anthropics/claude-plugins-official/tree/main/' + (plugin.type === 'official' ? 'plugins' : 'external_plugins') + '/' + escapeHtml(name) + '" target="_blank" class="marketplace-detail-link">View on GitHub</a></div>' +
|
||||
'<div style="font-size:11px;color:var(--vscode-descriptionForeground);margin-top:4px;">Adds <code style="font-size:10px;">' + escapeHtml(installId) + '</code> to .claude/settings.json</div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function backToPluginsList() {
|
||||
var grid = document.getElementById('pluginsGrid');
|
||||
if (pluginsDisplayedList) {
|
||||
grid.innerHTML = pluginsDisplayedList;
|
||||
} else {
|
||||
renderAvailablePlugins(topPlugins);
|
||||
}
|
||||
}
|
||||
|
||||
function installPlugin(installId) {
|
||||
sendStats('Plugin installed', { plugin: installId });
|
||||
vscode.postMessage({ type: 'installPlugin', installId: installId });
|
||||
hidePluginsModal();
|
||||
}
|
||||
|
||||
function removePlugin(installId) {
|
||||
sendStats('Plugin removed', { plugin: installId });
|
||||
vscode.postMessage({ type: 'removePlugin', installId: installId });
|
||||
}
|
||||
`;
|
||||
|
||||
export default getPluginsScript;
|
||||
26
src/plugins-ui.ts
Normal file
26
src/plugins-ui.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
const getPluginsHtml = () => `
|
||||
<!-- Plugins modal -->
|
||||
<div id="pluginsModal" class="tools-modal" style="display: none;">
|
||||
<div class="tools-modal-content">
|
||||
<div class="tools-modal-header">
|
||||
<span>Plugins</span>
|
||||
<button class="tools-close-btn" onclick="hidePluginsModal()">✕</button>
|
||||
</div>
|
||||
<div class="tools-list">
|
||||
<div class="mcp-servers-list" id="pluginsList">
|
||||
<!-- Installed plugins will be loaded here -->
|
||||
</div>
|
||||
<div class="mcp-popular-servers" id="pluginsMarketplace">
|
||||
<h4>Available Plugins</h4>
|
||||
<div class="marketplace-search">
|
||||
<input type="text" id="pluginsSearch" placeholder="Search plugins..." oninput="searchPlugins(this.value)" />
|
||||
</div>
|
||||
<div class="marketplace-grid" id="pluginsGrid">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export default getPluginsHtml;
|
||||
66
src/recommended-models.json
Normal file
66
src/recommended-models.json
Normal file
@@ -0,0 +1,66 @@
|
||||
[
|
||||
{
|
||||
"id": "openai/gpt-5.3-codex",
|
||||
"name": "GPT 5.3 Codex",
|
||||
"description": "Coding-focused GPT-5.3 variant with optimized routing.",
|
||||
"context_length": 400000,
|
||||
"max_output_tokens": 128000,
|
||||
"credits_per_request": 4.921875,
|
||||
"provider": "OpenAI",
|
||||
"quickLabel": "GPT",
|
||||
"tierModels": { "sonnet": "openai/gpt-5.3-codex", "opus": "openai/gpt-5.3-codex", "haiku": "openai/gpt-5.1-codex-mini" }
|
||||
},
|
||||
{
|
||||
"id": "google/gemini-3.1-pro-preview",
|
||||
"name": "Gemini 3.1 Pro Preview",
|
||||
"description": "Google's Gemini 3.1 Pro with enhanced reasoning and multimodal support.",
|
||||
"context_length": 1000000,
|
||||
"max_output_tokens": 64000,
|
||||
"credits_per_request": 4.375,
|
||||
"provider": "Google",
|
||||
"quickLabel": "Gemini",
|
||||
"tierModels": { "sonnet": "google/gemini-3.1-pro-preview", "opus": "google/gemini-3.1-pro-preview", "haiku": "google/gemini-3-flash" }
|
||||
},
|
||||
{
|
||||
"id": "minimax/minimax-m2.7",
|
||||
"name": "Minimax M2.7",
|
||||
"description": "MiniMax M2.7 with enhanced context understanding and improved complex tool use. Optimized for agentic workflows and long-horizon tasks.",
|
||||
"context_length": 204800,
|
||||
"max_output_tokens": 131000,
|
||||
"credits_per_request": 0.46875,
|
||||
"provider": "MiniMax",
|
||||
"quickLabel": "MiniMax"
|
||||
},
|
||||
{
|
||||
"id": "moonshotai/kimi-k2.5",
|
||||
"name": "Kimi K2.5",
|
||||
"description": "Kimi K2.5 is Moonshot AI's native multimodal model with strong general reasoning, visual coding, and agentic tool-calling.",
|
||||
"context_length": 262114,
|
||||
"max_output_tokens": 262114,
|
||||
"credits_per_request": 1.125,
|
||||
"provider": "Moonshot AI",
|
||||
"quickLabel": "Kimi",
|
||||
"tierModels": { "sonnet": "moonshotai/kimi-k2.5", "opus": "moonshotai/kimi-k2.5", "haiku": "moonshotai/kimi-k2-turbo" }
|
||||
},
|
||||
{
|
||||
"id": "zai/glm-5",
|
||||
"name": "GLM 5",
|
||||
"description": "GLM-5 is the latest GLM series text model with stronger reasoning, long-context chat, and reliable tool use.",
|
||||
"context_length": 202800,
|
||||
"max_output_tokens": 131100,
|
||||
"credits_per_request": 1.3125,
|
||||
"provider": "Zhipu AI",
|
||||
"quickLabel": "GLM",
|
||||
"tierModels": { "sonnet": "zai/glm-5", "opus": "zai/glm-5", "haiku": "zai/glm-4.7-flash" }
|
||||
},
|
||||
{
|
||||
"id": "deepseek/deepseek-v3.2-thinking",
|
||||
"name": "DeepSeek V3.2 Thinking",
|
||||
"description": "DeepSeek V3.2 thinking/reasoner mode. Reasoning-first model built for agents. First DeepSeek model with thinking-in-tool-use capability.",
|
||||
"context_length": 128000,
|
||||
"max_output_tokens": 64000,
|
||||
"credits_per_request": 0.21875,
|
||||
"provider": "DeepSeek",
|
||||
"quickLabel": "DeepSeek"
|
||||
}
|
||||
]
|
||||
265
src/router/formatRequest.ts
Normal file
265
src/router/formatRequest.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
interface MessageCreateParamsBase {
|
||||
model: string;
|
||||
messages: any[];
|
||||
system?: any;
|
||||
temperature?: number;
|
||||
tools?: any[];
|
||||
stream?: boolean;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates OpenAI format messages to ensure complete tool_calls/tool message pairing.
|
||||
* Requires tool messages to immediately follow assistant messages with tool_calls.
|
||||
* Enforces strict immediate following sequence between tool_calls and tool messages.
|
||||
*/
|
||||
function validateOpenAIToolCalls(messages: any[]): any[] {
|
||||
const validatedMessages: any[] = [];
|
||||
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const currentMessage = { ...messages[i] };
|
||||
|
||||
// Process assistant messages with tool_calls
|
||||
if (currentMessage.role === "assistant" && currentMessage.tool_calls) {
|
||||
const validToolCalls: any[] = [];
|
||||
const removedToolCallIds: string[] = [];
|
||||
|
||||
// Collect all immediately following tool messages
|
||||
const immediateToolMessages: any[] = [];
|
||||
let j = i + 1;
|
||||
while (j < messages.length && messages[j].role === "tool") {
|
||||
immediateToolMessages.push(messages[j]);
|
||||
j++;
|
||||
}
|
||||
|
||||
// For each tool_call, check if there's an immediately following tool message
|
||||
currentMessage.tool_calls.forEach((toolCall: any) => {
|
||||
const hasImmediateToolMessage = immediateToolMessages.some(toolMsg =>
|
||||
toolMsg.tool_call_id === toolCall.id
|
||||
);
|
||||
|
||||
if (hasImmediateToolMessage) {
|
||||
validToolCalls.push(toolCall);
|
||||
} else {
|
||||
removedToolCallIds.push(toolCall.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Update the assistant message
|
||||
if (validToolCalls.length > 0) {
|
||||
currentMessage.tool_calls = validToolCalls;
|
||||
} else {
|
||||
delete currentMessage.tool_calls;
|
||||
}
|
||||
|
||||
|
||||
// Only include message if it has content or valid tool_calls
|
||||
if (currentMessage.content || currentMessage.tool_calls) {
|
||||
validatedMessages.push(currentMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// Process tool messages
|
||||
else if (currentMessage.role === "tool") {
|
||||
let hasImmediateToolCall = false;
|
||||
|
||||
// Check if the immediately preceding assistant message has matching tool_call
|
||||
if (i > 0) {
|
||||
const prevMessage = messages[i - 1];
|
||||
if (prevMessage.role === "assistant" && prevMessage.tool_calls) {
|
||||
hasImmediateToolCall = prevMessage.tool_calls.some((toolCall: any) =>
|
||||
toolCall.id === currentMessage.tool_call_id
|
||||
);
|
||||
} else if (prevMessage.role === "tool") {
|
||||
// Check for assistant message before the sequence of tool messages
|
||||
for (let k = i - 1; k >= 0; k--) {
|
||||
if (messages[k].role === "tool") continue;
|
||||
if (messages[k].role === "assistant" && messages[k].tool_calls) {
|
||||
hasImmediateToolCall = messages[k].tool_calls.some((toolCall: any) =>
|
||||
toolCall.id === currentMessage.tool_call_id
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasImmediateToolCall) {
|
||||
validatedMessages.push(currentMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// For all other message types, include as-is
|
||||
else {
|
||||
validatedMessages.push(currentMessage);
|
||||
}
|
||||
}
|
||||
|
||||
return validatedMessages;
|
||||
}
|
||||
|
||||
// Model configuration - set from extension
|
||||
interface ModelConfig {
|
||||
haikuModel: string;
|
||||
sonnetModel: string;
|
||||
opusModel: string;
|
||||
}
|
||||
|
||||
let modelConfig: ModelConfig | null = null;
|
||||
|
||||
export function setModelConfig(config: ModelConfig): void {
|
||||
modelConfig = config;
|
||||
console.log('[Router] Model config updated:', config);
|
||||
}
|
||||
|
||||
export function mapModel(anthropicModel: string): string {
|
||||
console.log('[Router] Mapping model:', anthropicModel);
|
||||
|
||||
// If model already contains '/', it's already a provider model ID - return as-is
|
||||
if (anthropicModel.includes('/')) {
|
||||
console.log(`[Router] Model already has provider prefix, passing through: ${anthropicModel}`);
|
||||
return anthropicModel;
|
||||
}
|
||||
|
||||
if (!modelConfig) {
|
||||
console.log('[Router] No model config set, returning as-is');
|
||||
return anthropicModel;
|
||||
}
|
||||
|
||||
if (anthropicModel.includes('haiku') && modelConfig.haikuModel) {
|
||||
console.log(`[Router] Mapping haiku -> ${modelConfig.haikuModel}`);
|
||||
return modelConfig.haikuModel;
|
||||
} else if (anthropicModel.includes('sonnet') && modelConfig.sonnetModel) {
|
||||
console.log(`[Router] Mapping sonnet -> ${modelConfig.sonnetModel}`);
|
||||
return modelConfig.sonnetModel;
|
||||
} else if (anthropicModel.includes('opus') && modelConfig.opusModel) {
|
||||
console.log(`[Router] Mapping opus -> ${modelConfig.opusModel}`);
|
||||
return modelConfig.opusModel;
|
||||
}
|
||||
|
||||
console.log(`[Router] No mapping found for model: ${anthropicModel}, passing through`);
|
||||
return anthropicModel;
|
||||
}
|
||||
|
||||
export function formatAnthropicToOpenAI(body: MessageCreateParamsBase): any {
|
||||
const { model, messages, system = [], temperature, tools, stream } = body;
|
||||
|
||||
const openAIMessages = Array.isArray(messages)
|
||||
? messages.flatMap((anthropicMessage) => {
|
||||
const openAiMessagesFromThisAnthropicMessage: any[] = [];
|
||||
|
||||
if (!Array.isArray(anthropicMessage.content)) {
|
||||
if (typeof anthropicMessage.content === "string") {
|
||||
openAiMessagesFromThisAnthropicMessage.push({
|
||||
role: anthropicMessage.role,
|
||||
content: anthropicMessage.content,
|
||||
});
|
||||
}
|
||||
return openAiMessagesFromThisAnthropicMessage;
|
||||
}
|
||||
|
||||
if (anthropicMessage.role === "assistant") {
|
||||
const assistantMessage: any = {
|
||||
role: "assistant",
|
||||
content: null,
|
||||
};
|
||||
let textContent = "";
|
||||
const toolCalls: any[] = [];
|
||||
|
||||
anthropicMessage.content.forEach((contentPart: any) => {
|
||||
if (contentPart.type === "text") {
|
||||
textContent += (typeof contentPart.text === "string"
|
||||
? contentPart.text
|
||||
: JSON.stringify(contentPart.text)) + "\n";
|
||||
} else if (contentPart.type === "tool_use") {
|
||||
toolCalls.push({
|
||||
id: contentPart.id,
|
||||
type: "function",
|
||||
function: {
|
||||
name: contentPart.name,
|
||||
arguments: JSON.stringify(contentPart.input),
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const trimmedTextContent = textContent.trim();
|
||||
if (trimmedTextContent.length > 0) {
|
||||
assistantMessage.content = trimmedTextContent;
|
||||
}
|
||||
if (toolCalls.length > 0) {
|
||||
assistantMessage.tool_calls = toolCalls;
|
||||
}
|
||||
if (assistantMessage.content || (assistantMessage.tool_calls && assistantMessage.tool_calls.length > 0)) {
|
||||
openAiMessagesFromThisAnthropicMessage.push(assistantMessage);
|
||||
}
|
||||
} else if (anthropicMessage.role === "user") {
|
||||
let userTextMessageContent = "";
|
||||
const subsequentToolMessages: any[] = [];
|
||||
|
||||
anthropicMessage.content.forEach((contentPart: any) => {
|
||||
if (contentPart.type === "text") {
|
||||
userTextMessageContent += (typeof contentPart.text === "string"
|
||||
? contentPart.text
|
||||
: JSON.stringify(contentPart.text)) + "\n";
|
||||
} else if (contentPart.type === "tool_result") {
|
||||
subsequentToolMessages.push({
|
||||
role: "tool",
|
||||
tool_call_id: contentPart.tool_use_id,
|
||||
content: typeof contentPart.content === "string"
|
||||
? contentPart.content
|
||||
: JSON.stringify(contentPart.content),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const trimmedUserText = userTextMessageContent.trim();
|
||||
if (trimmedUserText.length > 0) {
|
||||
openAiMessagesFromThisAnthropicMessage.push({
|
||||
role: "user",
|
||||
content: trimmedUserText,
|
||||
});
|
||||
}
|
||||
openAiMessagesFromThisAnthropicMessage.push(...subsequentToolMessages);
|
||||
}
|
||||
return openAiMessagesFromThisAnthropicMessage;
|
||||
})
|
||||
: [];
|
||||
|
||||
const systemMessages = Array.isArray(system)
|
||||
? system.map((item) => ({
|
||||
role: "system",
|
||||
content: typeof item === "string" ? item : item.text
|
||||
}))
|
||||
: typeof system === "string" && system.length > 0
|
||||
? [{ role: "system", content: system }]
|
||||
: [];
|
||||
|
||||
const data: any = {
|
||||
model: mapModel(model),
|
||||
messages: [...systemMessages, ...openAIMessages],
|
||||
temperature,
|
||||
stream,
|
||||
};
|
||||
|
||||
// Request usage stats in streaming responses
|
||||
if (stream) {
|
||||
data.stream_options = { include_usage: true };
|
||||
}
|
||||
|
||||
if (tools) {
|
||||
data.tools = tools.map((item: any) => ({
|
||||
type: "function",
|
||||
function: {
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
parameters: item.input_schema,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
// Validate OpenAI messages to ensure complete tool_calls/tool message pairing
|
||||
data.messages = [...systemMessages, ...validateOpenAIToolCalls(openAIMessages)];
|
||||
|
||||
return data;
|
||||
}
|
||||
37
src/router/formatResponse.ts
Normal file
37
src/router/formatResponse.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export function formatOpenAIToAnthropic(completion: any, model: string): any {
|
||||
const messageId = "msg_" + Date.now();
|
||||
const message = completion.choices[0].message;
|
||||
|
||||
const content: any[] = [];
|
||||
if (message.content) {
|
||||
content.push({ text: message.content, type: "text" });
|
||||
}
|
||||
if (message.tool_calls) {
|
||||
for (const item of message.tool_calls) {
|
||||
content.push({
|
||||
type: 'tool_use',
|
||||
id: item.id,
|
||||
name: item.function?.name,
|
||||
input: item.function?.arguments ? JSON.parse(item.function.arguments) : {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const hasToolUse = message.tool_calls && message.tool_calls.length > 0;
|
||||
const usage = completion.usage || {};
|
||||
|
||||
const result = {
|
||||
id: messageId,
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: content,
|
||||
stop_reason: hasToolUse ? "tool_use" : "end_turn",
|
||||
stop_sequence: null,
|
||||
model,
|
||||
usage: {
|
||||
input_tokens: usage.prompt_tokens || 0,
|
||||
output_tokens: usage.completion_tokens || 0,
|
||||
},
|
||||
};
|
||||
return result;
|
||||
}
|
||||
2
src/router/index.ts
Normal file
2
src/router/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { startRouter, stopRouter, isRouterRunning, getRouterPort, setBaseUrl } from './server';
|
||||
export { setModelConfig } from './formatRequest';
|
||||
220
src/router/server.ts
Normal file
220
src/router/server.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
import * as http from 'http';
|
||||
import { formatAnthropicToOpenAI } from './formatRequest';
|
||||
import { streamOpenAIToAnthropic } from './streamResponse';
|
||||
import { formatOpenAIToAnthropic } from './formatResponse';
|
||||
|
||||
const DEFAULT_PORT = 31548;
|
||||
const DEFAULT_BASE_URL = "http://localhost:8787/v1";
|
||||
|
||||
let server: http.Server | null = null;
|
||||
let currentPort: number = DEFAULT_PORT;
|
||||
let baseUrl: string = DEFAULT_BASE_URL;
|
||||
|
||||
export function setBaseUrl(url: string): void {
|
||||
baseUrl = url || DEFAULT_BASE_URL;
|
||||
console.log('[Router] Base URL set to:', baseUrl);
|
||||
}
|
||||
|
||||
// Helper to parse JSON body
|
||||
async function parseBody(req: http.IncomingMessage): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let body = '';
|
||||
req.on('data', chunk => {
|
||||
body += chunk.toString();
|
||||
// Prevent payload too large (50MB limit)
|
||||
if (body.length > 50 * 1024 * 1024) {
|
||||
req.destroy();
|
||||
reject(new Error('Payload too large'));
|
||||
}
|
||||
});
|
||||
req.on('end', () => {
|
||||
try {
|
||||
resolve(body ? JSON.parse(body) : {});
|
||||
} catch (e) {
|
||||
reject(new Error('Invalid JSON'));
|
||||
}
|
||||
});
|
||||
req.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
function createServer(): http.Server {
|
||||
return http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url || '/', `http://${req.headers.host}`);
|
||||
const method = req.method || 'GET';
|
||||
|
||||
try {
|
||||
// POST /v1/messages
|
||||
if (url.pathname === '/v1/messages' && method === 'POST') {
|
||||
console.log('[Router] 📥 Received request to /v1/messages');
|
||||
|
||||
const anthropicRequest = await parseBody(req);
|
||||
const openaiRequest = formatAnthropicToOpenAI(anthropicRequest);
|
||||
|
||||
console.log('[Router] 🔄 Converted to OpenAI format:', {
|
||||
model: openaiRequest.model,
|
||||
stream: openaiRequest.stream,
|
||||
messageCount: openaiRequest.messages?.length
|
||||
});
|
||||
|
||||
const bearerToken = (req.headers['x-api-key'] as string) ||
|
||||
(req.headers.authorization as string)?.replace("Bearer ", "").replace("bearer ", "");
|
||||
|
||||
if (!bearerToken || bearerToken.trim() === '') {
|
||||
console.log('[Router] ❌ No bearer token found');
|
||||
res.writeHead(401, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
type: 'error',
|
||||
error: {
|
||||
type: 'authentication_error',
|
||||
message: 'No API key provided. Please configure your OpenCredits user key in environment variables.'
|
||||
}
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchHeaders = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": `Bearer ${bearerToken}`,
|
||||
"HTTP-Referer": "https://claude-code-chat.local",
|
||||
"X-Title": "Claude-Code-Chat-Router"
|
||||
};
|
||||
|
||||
const openaiResponse = await fetch(`${baseUrl}/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: fetchHeaders,
|
||||
body: JSON.stringify(openaiRequest),
|
||||
});
|
||||
|
||||
console.log('[Router] 📥 Response status:', openaiResponse.status);
|
||||
|
||||
if (!openaiResponse.ok) {
|
||||
const errorText = await openaiResponse.text();
|
||||
console.log('[Router] ❌ Error:', errorText);
|
||||
|
||||
// Try to parse as JSON, otherwise use raw text
|
||||
let errorMessage = errorText;
|
||||
try {
|
||||
const parsed = JSON.parse(errorText);
|
||||
errorMessage = parsed.error?.message || parsed.message || errorText;
|
||||
} catch {}
|
||||
|
||||
res.writeHead(openaiResponse.status, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
type: 'error',
|
||||
error: {
|
||||
type: openaiResponse.status === 401 ? 'authentication_error' : 'api_error',
|
||||
message: `[Router] ${errorMessage}`
|
||||
}
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
if (openaiRequest.stream) {
|
||||
console.log('[Router] 🌊 Starting stream response');
|
||||
const anthropicStream = streamOpenAIToAnthropic(
|
||||
openaiResponse.body as ReadableStream,
|
||||
openaiRequest.model
|
||||
);
|
||||
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
});
|
||||
|
||||
const reader = anthropicStream.getReader();
|
||||
|
||||
const pump = async () => {
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
res.end();
|
||||
break;
|
||||
}
|
||||
res.write(value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Router] Stream error:', error);
|
||||
res.end();
|
||||
}
|
||||
};
|
||||
|
||||
pump();
|
||||
} else {
|
||||
const openaiData = await openaiResponse.json();
|
||||
const anthropicResponse = formatOpenAIToAnthropic(openaiData, openaiRequest.model);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(anthropicResponse));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 404 Not Found
|
||||
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
||||
res.end('Not Found');
|
||||
} catch (error) {
|
||||
console.error('[Router] Error processing request:', error);
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
type: 'error',
|
||||
error: {
|
||||
type: 'api_error',
|
||||
message: `[Router] Internal error: ${(error as Error).message}`
|
||||
}
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function startRouter(port: number = DEFAULT_PORT): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (server) {
|
||||
console.log('[Router] Already running on port', currentPort);
|
||||
resolve(currentPort);
|
||||
return;
|
||||
}
|
||||
|
||||
server = createServer();
|
||||
|
||||
server.on('error', (err: NodeJS.ErrnoException) => {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
console.log(`[Router] Port ${port} in use, trying ${port + 1}`);
|
||||
server = null;
|
||||
startRouter(port + 1).then(resolve).catch(reject);
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(port, () => {
|
||||
currentPort = port;
|
||||
console.log(`[Router] 🚀 Running on http://localhost:${port}`);
|
||||
resolve(port);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function stopRouter(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
if (!server) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
server.close(() => {
|
||||
console.log('[Router] Stopped');
|
||||
server = null;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function isRouterRunning(): boolean {
|
||||
return server !== null;
|
||||
}
|
||||
|
||||
export function getRouterPort(): number {
|
||||
return currentPort;
|
||||
}
|
||||
219
src/router/streamResponse.ts
Normal file
219
src/router/streamResponse.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
export function streamOpenAIToAnthropic(openaiStream: ReadableStream, model: string): ReadableStream {
|
||||
const messageId = "msg_" + Date.now();
|
||||
|
||||
const enqueueSSE = (controller: ReadableStreamDefaultController, eventType: string, data: any) => {
|
||||
const sseMessage = `event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n`;
|
||||
controller.enqueue(new TextEncoder().encode(sseMessage));
|
||||
};
|
||||
|
||||
return new ReadableStream({
|
||||
async start(controller) {
|
||||
// Send message_start event
|
||||
const messageStart = {
|
||||
type: "message_start",
|
||||
message: {
|
||||
id: messageId,
|
||||
type: "message",
|
||||
role: "assistant",
|
||||
content: [],
|
||||
model,
|
||||
stop_reason: null,
|
||||
stop_sequence: null,
|
||||
usage: { input_tokens: 0, output_tokens: 0 },
|
||||
},
|
||||
};
|
||||
enqueueSSE(controller, "message_start", messageStart);
|
||||
|
||||
let contentBlockIndex = 0;
|
||||
let hasAnyBlock = false;
|
||||
let hasStartedTextBlock = false;
|
||||
let isToolUse = false;
|
||||
let currentToolCallId: string | null = null;
|
||||
let toolCallJsonMap = new Map<string, string>();
|
||||
let streamUsage: { input_tokens: number; output_tokens: number } | null = null;
|
||||
|
||||
const reader = openaiStream.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
// Process any remaining data in buffer
|
||||
if (buffer.trim()) {
|
||||
const lines = buffer.split('\n');
|
||||
for (const line of lines) {
|
||||
if (line.trim() && line.startsWith('data: ')) {
|
||||
const data = line.slice(6).trim();
|
||||
if (data === '[DONE]') continue;
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
processStreamChunk(parsed);
|
||||
} catch (e) {
|
||||
// Parse error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Decode chunk and add to buffer
|
||||
const chunk = decoder.decode(value, { stream: true });
|
||||
buffer += chunk;
|
||||
|
||||
// Process complete lines from buffer
|
||||
const lines = buffer.split('\n');
|
||||
// Keep the last potentially incomplete line in buffer
|
||||
buffer = lines.pop() || '';
|
||||
|
||||
// Process complete lines in order
|
||||
for (const line of lines) {
|
||||
if (line.trim() && line.startsWith('data: ')) {
|
||||
const data = line.slice(6).trim();
|
||||
if (data === '[DONE]') continue;
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
processStreamChunk(parsed);
|
||||
} catch (e) {
|
||||
// Parse error
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
|
||||
function processStreamChunk(parsed: any) {
|
||||
// Capture usage from the chunk if available
|
||||
if (parsed.usage) {
|
||||
streamUsage = {
|
||||
input_tokens: parsed.usage.prompt_tokens || 0,
|
||||
output_tokens: parsed.usage.completion_tokens || 0,
|
||||
};
|
||||
}
|
||||
|
||||
const delta = parsed.choices?.[0]?.delta;
|
||||
if (delta) {
|
||||
processStreamDelta(delta);
|
||||
}
|
||||
}
|
||||
|
||||
function closeCurrentBlock() {
|
||||
if (hasAnyBlock) {
|
||||
enqueueSSE(controller, "content_block_stop", {
|
||||
type: "content_block_stop",
|
||||
index: contentBlockIndex,
|
||||
});
|
||||
contentBlockIndex++;
|
||||
}
|
||||
hasAnyBlock = true;
|
||||
}
|
||||
|
||||
function processStreamDelta(delta: any) {
|
||||
|
||||
// Handle tool calls
|
||||
if (delta.tool_calls?.length > 0) {
|
||||
for (const toolCall of delta.tool_calls) {
|
||||
const toolCallId = toolCall.id;
|
||||
|
||||
if (toolCallId && toolCallId !== currentToolCallId) {
|
||||
closeCurrentBlock();
|
||||
|
||||
isToolUse = true;
|
||||
hasStartedTextBlock = false;
|
||||
currentToolCallId = toolCallId;
|
||||
toolCallJsonMap.set(toolCallId, "");
|
||||
|
||||
const toolBlock = {
|
||||
type: "tool_use",
|
||||
id: toolCallId,
|
||||
name: toolCall.function?.name,
|
||||
input: {},
|
||||
};
|
||||
|
||||
enqueueSSE(controller, "content_block_start", {
|
||||
type: "content_block_start",
|
||||
index: contentBlockIndex,
|
||||
content_block: toolBlock,
|
||||
});
|
||||
}
|
||||
|
||||
if (toolCall.function?.arguments && currentToolCallId) {
|
||||
const currentJson = toolCallJsonMap.get(currentToolCallId) || "";
|
||||
toolCallJsonMap.set(currentToolCallId, currentJson + toolCall.function.arguments);
|
||||
|
||||
enqueueSSE(controller, "content_block_delta", {
|
||||
type: "content_block_delta",
|
||||
index: contentBlockIndex,
|
||||
delta: {
|
||||
type: "input_json_delta",
|
||||
partial_json: toolCall.function.arguments,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (delta.content) {
|
||||
if (isToolUse) {
|
||||
closeCurrentBlock();
|
||||
isToolUse = false;
|
||||
currentToolCallId = null;
|
||||
}
|
||||
|
||||
if (!hasStartedTextBlock) {
|
||||
if (!hasAnyBlock) {
|
||||
hasAnyBlock = true;
|
||||
}
|
||||
enqueueSSE(controller, "content_block_start", {
|
||||
type: "content_block_start",
|
||||
index: contentBlockIndex,
|
||||
content_block: {
|
||||
type: "text",
|
||||
text: "",
|
||||
},
|
||||
});
|
||||
hasStartedTextBlock = true;
|
||||
}
|
||||
|
||||
enqueueSSE(controller, "content_block_delta", {
|
||||
type: "content_block_delta",
|
||||
index: contentBlockIndex,
|
||||
delta: {
|
||||
type: "text_delta",
|
||||
text: delta.content,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Close last content block
|
||||
if (hasAnyBlock) {
|
||||
enqueueSSE(controller, "content_block_stop", {
|
||||
type: "content_block_stop",
|
||||
index: contentBlockIndex,
|
||||
});
|
||||
}
|
||||
|
||||
// Send message_delta and message_stop
|
||||
enqueueSSE(controller, "message_delta", {
|
||||
type: "message_delta",
|
||||
delta: {
|
||||
stop_reason: isToolUse ? "tool_use" : "end_turn",
|
||||
stop_sequence: null,
|
||||
},
|
||||
usage: streamUsage || { input_tokens: 0, output_tokens: 0 },
|
||||
});
|
||||
|
||||
enqueueSSE(controller, "message_stop", {
|
||||
type: "message_stop",
|
||||
});
|
||||
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
}
|
||||
5295
src/script.ts
Normal file
5295
src/script.ts
Normal file
File diff suppressed because it is too large
Load Diff
288
src/skills-script.ts
Normal file
288
src/skills-script.ts
Normal file
@@ -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 ? '<span class="marketplace-item-stars">' + (installs >= 1000 ? (Math.round(installs / 100) / 10) + 'k' : installs) + ' installs</span>' : '';
|
||||
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 += '<div class="marketplace-item" data-skill-id="' + safeId + '" data-skill-source="' + escapeHtml(source) + '" data-skill-name="' + escapeHtml(name) + '" data-skill-rawurl="' + escapeHtml(rawUrl) + '" data-skill-installs="' + escapeHtml(installsText) + '" onclick="installSkillFromMarketplace(this)">' +
|
||||
'<div class="marketplace-item-header">' +
|
||||
'<div class="marketplace-item-icon-placeholder">' + escapeHtml(name.charAt(0).toUpperCase()) + '</div>' +
|
||||
'<div class="marketplace-item-info">' +
|
||||
'<div class="marketplace-item-name">' + escapeHtml(name) + '</div>' +
|
||||
'<div class="marketplace-item-meta">' + installsHtml + '</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="marketplace-item-desc">' + escapeHtml(source) + '</div>' +
|
||||
'</div>';
|
||||
});
|
||||
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 = '<div class="no-servers">' +
|
||||
'<div class="no-servers-icon"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg></div>' +
|
||||
'<div class="no-servers-text">No skills installed</div>' +
|
||||
'<button class="btn outlined no-servers-btn" onclick="showSkillAddForm()">+ Create skill</button>' +
|
||||
'</div>';
|
||||
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 = '<div class="skill-item-row">' +
|
||||
'<div class="skill-item-info">' +
|
||||
'<div class="server-name">' + escapeHtml(skill.name) + ' <span class="server-type">' + escapeHtml(skill.scope) + '</span></div>' +
|
||||
'<div class="skill-item-desc">' + escapeHtml(desc) + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="server-actions" style="flex-shrink:0;">' +
|
||||
'<button class="btn outlined" style="font-size:11px;padding:3px 8px;" onclick="toggleSkillDetail(\\'' + detailId + '\\')">Details</button>' +
|
||||
'<button class="btn outlined server-delete-btn" data-skill="' + escapeHtml(skill.name) + '" data-scope="' + escapeHtml(skill.scope) + '" onclick="deleteSkill(this.dataset.skill, this.dataset.scope)">Delete</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div id="' + detailId + '" class="skill-detail-content" style="display:none;">' +
|
||||
'<pre style="white-space:pre-wrap;font-size:11px;color:var(--vscode-descriptionForeground);margin:8px 0 0;max-height:200px;overflow-y:auto;">' + escapeHtml(content) + '</pre>' +
|
||||
'</div>';
|
||||
skillsList.appendChild(item);
|
||||
});
|
||||
|
||||
// Add create button at bottom
|
||||
var addDiv = document.createElement('div');
|
||||
addDiv.className = 'mcp-add-server';
|
||||
addDiv.innerHTML = '<button class="btn outlined" onclick="showSkillAddForm()">+ Create skill</button>';
|
||||
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 = '<div class="marketplace-loading">Searching...</div>';
|
||||
}
|
||||
// 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 = '<div class="marketplace-loading">No skills found.</div>';
|
||||
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 ? '<span class="marketplace-item-stars">' + (installs >= 1000 ? (Math.round(installs / 100) / 10) + 'k' : installs) + ' installs</span>' : '';
|
||||
|
||||
var rawUrl = skill.rawUrl || '';
|
||||
var installsText = installs >= 1000 ? (Math.round(installs / 100) / 10) + 'k installs' : (installs > 0 ? installs + ' installs' : '');
|
||||
html += '<div class="marketplace-item" data-skill-id="' + safeId + '" data-skill-source="' + escapeHtml(source) + '" data-skill-name="' + escapeHtml(name) + '" data-skill-rawurl="' + escapeHtml(rawUrl) + '" data-skill-installs="' + escapeHtml(installsText) + '" onclick="installSkillFromMarketplace(this)">' +
|
||||
'<div class="marketplace-item-header">' +
|
||||
'<div class="marketplace-item-icon-placeholder">' + escapeHtml(name.charAt(0).toUpperCase()) + '</div>' +
|
||||
'<div class="marketplace-item-info">' +
|
||||
'<div class="marketplace-item-name">' + escapeHtml(name) + '</div>' +
|
||||
'<div class="marketplace-item-meta">' + installsHtml + '</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="marketplace-item-desc">' + escapeHtml(source) + '</div>' +
|
||||
'</div>';
|
||||
});
|
||||
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 ? '<span class="marketplace-item-stars">' + installs + '</span>' : '';
|
||||
|
||||
var grid = document.getElementById('skillsGrid');
|
||||
// Save current grid content to restore on back
|
||||
skillsDisplayedList = grid.innerHTML;
|
||||
|
||||
grid.innerHTML = '<div class="marketplace-detail">' +
|
||||
'<button class="marketplace-back-btn" onclick="backToSkillsList()">← Back</button>' +
|
||||
'<div class="marketplace-detail-header">' +
|
||||
'<div class="marketplace-item-icon-placeholder" style="width:40px;height:40px;font-size:18px;">' + escapeHtml(name.charAt(0).toUpperCase()) + '</div>' +
|
||||
'<div class="marketplace-detail-header-info">' +
|
||||
'<div class="marketplace-detail-name">' + escapeHtml(name) + '</div>' +
|
||||
'<div class="marketplace-detail-header-meta">' +
|
||||
installsHtml +
|
||||
'<a href="' + escapeHtml(repoUrl) + '" target="_blank" class="marketplace-detail-link">GitHub</a>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="marketplace-detail-desc">' + escapeHtml('Source: ' + source) + '</div>' +
|
||||
'<div class="marketplace-detail-config">' +
|
||||
'<div class="marketplace-detail-section-title">Install to</div>' +
|
||||
'<div class="form-group" style="margin:0;">' +
|
||||
'<select id="skillInstallScope">' +
|
||||
'<option value="project">Project (.claude/skills/)</option>' +
|
||||
'<option value="global">Global (~/.claude/skills/)</option>' +
|
||||
'</select>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="marketplace-detail-actions" style="margin-top:12px;">' +
|
||||
'<button class="btn" data-source="' + escapeHtml(source) + '" data-name="' + escapeHtml(name) + '" onclick="confirmSkillInstall(this)">Install</button>' +
|
||||
'<div style="font-size:11px;color:var(--vscode-descriptionForeground);margin-top:6px;">Opens a terminal running <code style="font-size:10px;">npx skills add</code> via <a href="https://skills.sh" target="_blank" class="marketplace-detail-link">skills.sh</a></div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
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;
|
||||
51
src/skills-ui.ts
Normal file
51
src/skills-ui.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
const getSkillsHtml = () => `
|
||||
<!-- Skills modal -->
|
||||
<div id="skillsModal" class="tools-modal" style="display: none;">
|
||||
<div class="tools-modal-content">
|
||||
<div class="tools-modal-header">
|
||||
<span>Skills</span>
|
||||
<button class="tools-close-btn" onclick="hideSkillsModal()">✕</button>
|
||||
</div>
|
||||
<div class="tools-list">
|
||||
<div class="mcp-servers-list" id="skillsList">
|
||||
<!-- Installed skills will be loaded here -->
|
||||
</div>
|
||||
<div class="mcp-popular-servers" id="skillsMarketplace">
|
||||
<h4>Search Skills</h4>
|
||||
<div class="marketplace-search">
|
||||
<input type="text" id="skillsSearch" placeholder="Search skills..." oninput="searchSkills(this.value)" />
|
||||
</div>
|
||||
<div class="marketplace-grid" id="skillsGrid">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mcp-add-form" id="skillAddForm" style="display: none;">
|
||||
<div class="form-group">
|
||||
<label for="skillName">Skill Name:</label>
|
||||
<input type="text" id="skillName" placeholder="my-skill" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="skillDescription">Description:</label>
|
||||
<input type="text" id="skillDescription" placeholder="What this skill does">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="skillScope">Scope:</label>
|
||||
<select id="skillScope">
|
||||
<option value="personal">Personal (~/.claude/skills/)</option>
|
||||
<option value="project">Project (.claude/skills/)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="skillContent">Instructions (Markdown):</label>
|
||||
<textarea id="skillContent" placeholder="Instructions for Claude to follow when this skill is invoked..." rows="8"></textarea>
|
||||
</div>
|
||||
<div class="form-buttons">
|
||||
<button class="btn" onclick="saveSkill()">Create Skill</button>
|
||||
<button class="btn outlined" onclick="hideSkillAddForm()">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export default getSkillsHtml;
|
||||
479
src/top-mcp-servers.json
Normal file
479
src/top-mcp-servers.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
240
src/top-plugins.json
Normal file
240
src/top-plugins.json
Normal file
@@ -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"
|
||||
}
|
||||
]
|
||||
289
src/top-skills.json
Normal file
289
src/top-skills.json
Normal file
@@ -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"
|
||||
}
|
||||
]
|
||||
3778
src/ui-styles.ts
3778
src/ui-styles.ts
File diff suppressed because it is too large
Load Diff
@@ -6,12 +6,19 @@
|
||||
"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. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"mcp-permissions.js",
|
||||
"claude-code-chat-permissions-mcp",
|
||||
"backup-files"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user