mirror of
https://github.com/cline/cline.git
synced 2026-09-06 12:28:08 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c6129782f |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix: make AWS Bedrock authentication predictable
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add translation to CODE_OF_CONDUCT, CONTRIBUTING and README to Arabic ar-sa.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
A new MCP Marketplace display setting has been added to VSCode settings. (It is defaulted to "true")
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Added timeout configuration for individual MCP servers.
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Improve Requesty provider integration
|
||||
|
||||
- Adding Cline headers to API requests, to enable targeted optimizations
|
||||
- Read o3 reasoning effort from Cline config, not model name
|
||||
- Show token information in task header
|
||||
- Get total cost from response when available
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
This version of cline adds optional telemetry. If you opt in, cline will use anonymous interaction data to improve our product.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Adjustment to MCP server installation prompt
|
||||
+1
-1
@@ -7,4 +7,4 @@ tmp
|
||||
|
||||
.DS_Store
|
||||
|
||||
pnpm-lock.yaml
|
||||
pnpm-lock.yaml
|
||||
|
||||
+1
-9
@@ -1,18 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## [3.4.6]
|
||||
|
||||
- Add support for Claude 3.7 Sonnet
|
||||
|
||||
## [3.4.0]
|
||||
|
||||
- Introducing MCP Marketplace! You can now discover and install the best MCP servers right from within the extension, with new servers added regularly
|
||||
- Add mermaid diagram support in Plan mode! You can now see visual representations of mermaid code blocks in chat, and click on them to see an expanded view
|
||||
- Use more visual checkpoints indicators after editing files & running commands
|
||||
- Create a checkpoint at the beginning of each task to easily revert to the initial state
|
||||
- Send current textfield contents as additional feedback when toggling from Plan to Act Mode, or when hitting 'Approve' button
|
||||
- Add 'Terminal' context mention to reference the active terminal's contents
|
||||
- Add 'Git Commits' context mention to reference current working changes or specific commits (thanks @mrubens!)
|
||||
- Send current textfield contents as additional feedback when toggling from Plan to Act Mode, or when hitting 'Approve' button
|
||||
- Add advanced configuration options for OpenAI Compatible (context window, max output, pricing, etc.)
|
||||
- Add Alibaba Qwen 2.5 coder models, VL models, and DeepSeek-R1/V3 support
|
||||
- Improve support for AWS Bedrock Profiles
|
||||
|
||||
+2
-1
@@ -9,6 +9,7 @@ Cline Bot Inc. ("Cline," "we," "our," and/or "us") values the privacy of individ
|
||||
- Your data is only sent to your chosen AI provider (e.g., Anthropic, OpenAI) when you explicitly request assistance
|
||||
- All processing happens locally on your machine
|
||||
- API keys are stored securely in VS Code's built-in settings storage
|
||||
- Telemetry is collected anonymously via PostHog if the user opts in
|
||||
|
||||
## Information We Process
|
||||
|
||||
@@ -43,7 +44,7 @@ Cline functions solely as a client-side VS Code extension that facilitates commu
|
||||
|
||||
- All operations happen on your local machine
|
||||
- No central servers or data collection
|
||||
- No telemetry or usage statistics gathered
|
||||
- No telemetry or usage statistics gathered unless the user opts in
|
||||
- No account creation required
|
||||
|
||||
2. **API Key Security**:
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ Welcome to the Cline documentation - your comprehensive guide to using and exten
|
||||
|
||||
- **Interested in contributing?** We welcome your input:
|
||||
- Feel free to submit a pull request
|
||||
- [Contribution Guidelines](../CONTRIBUTING.md)
|
||||
- [Contribution Guidelines](CONTRIBUTING.md)
|
||||
|
||||
## Additional Resources
|
||||
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
# Cline's Memory Bank
|
||||
|
||||
I am Cline, an expert software engineer with a unique characteristic: my memory resets completely between sessions. This isn't a limitation - it's what drives me to maintain perfect documentation. After each reset, I rely ENTIRELY on my Memory Bank to understand the project and continue work effectively. I MUST read ALL memory bank files at the start of EVERY task - this is not optional.
|
||||
|
||||
## Memory Bank Structure
|
||||
|
||||
The Memory Bank consists of required core files and optional context files, all in Markdown format. Files build upon each other in a clear hierarchy:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
PB[projectbrief.md] --> PC[productContext.md]
|
||||
PB --> SP[systemPatterns.md]
|
||||
PB --> TC[techContext.md]
|
||||
|
||||
PC --> AC[activeContext.md]
|
||||
SP --> AC
|
||||
TC --> AC
|
||||
|
||||
AC --> P[progress.md]
|
||||
```
|
||||
|
||||
### Core Files (Required)
|
||||
1. `projectbrief.md`
|
||||
- Foundation document that shapes all other files
|
||||
- Created at project start if it doesn't exist
|
||||
- Defines core requirements and goals
|
||||
- Source of truth for project scope
|
||||
|
||||
2. `productContext.md`
|
||||
- Why this project exists
|
||||
- Problems it solves
|
||||
- How it should work
|
||||
- User experience goals
|
||||
|
||||
3. `activeContext.md`
|
||||
- Current work focus
|
||||
- Recent changes
|
||||
- Next steps
|
||||
- Active decisions and considerations
|
||||
|
||||
4. `systemPatterns.md`
|
||||
- System architecture
|
||||
- Key technical decisions
|
||||
- Design patterns in use
|
||||
- Component relationships
|
||||
|
||||
5. `techContext.md`
|
||||
- Technologies used
|
||||
- Development setup
|
||||
- Technical constraints
|
||||
- Dependencies
|
||||
|
||||
6. `progress.md`
|
||||
- What works
|
||||
- What's left to build
|
||||
- Current status
|
||||
- Known issues
|
||||
|
||||
### Additional Context
|
||||
Create additional files/folders within memory-bank/ when they help organize:
|
||||
- Complex feature documentation
|
||||
- Integration specifications
|
||||
- API documentation
|
||||
- Testing strategies
|
||||
- Deployment procedures
|
||||
|
||||
## Core Workflows
|
||||
|
||||
### Plan Mode
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start[Start] --> ReadFiles[Read Memory Bank]
|
||||
ReadFiles --> CheckFiles{Files Complete?}
|
||||
|
||||
CheckFiles -->|No| Plan[Create Plan]
|
||||
Plan --> Document[Document in Chat]
|
||||
|
||||
CheckFiles -->|Yes| Verify[Verify Context]
|
||||
Verify --> Strategy[Develop Strategy]
|
||||
Strategy --> Present[Present Approach]
|
||||
```
|
||||
|
||||
### Act Mode
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start[Start] --> Context[Check Memory Bank]
|
||||
Context --> Update[Update Documentation]
|
||||
Update --> Rules[Update .clinerules if needed]
|
||||
Rules --> Execute[Execute Task]
|
||||
Execute --> Document[Document Changes]
|
||||
```
|
||||
|
||||
## Documentation Updates
|
||||
|
||||
Memory Bank updates occur when:
|
||||
1. Discovering new project patterns
|
||||
2. After implementing significant changes
|
||||
3. When user requests with **update memory bank** (MUST review ALL files)
|
||||
4. When context needs clarification
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start[Update Process]
|
||||
|
||||
subgraph Process
|
||||
P1[Review ALL Files]
|
||||
P2[Document Current State]
|
||||
P3[Clarify Next Steps]
|
||||
P4[Update .clinerules]
|
||||
|
||||
P1 --> P2 --> P3 --> P4
|
||||
end
|
||||
|
||||
Start --> Process
|
||||
```
|
||||
|
||||
Note: When triggered by **update memory bank**, I MUST review every memory bank file, even if some don't require updates. Focus particularly on activeContext.md and progress.md as they track current state.
|
||||
|
||||
## Project Intelligence (.clinerules)
|
||||
|
||||
The .clinerules file is my learning journal for each project. It captures important patterns, preferences, and project intelligence that help me work more effectively. As I work with you and the project, I'll discover and document key insights that aren't obvious from the code alone.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start{Discover New Pattern}
|
||||
|
||||
subgraph Learn [Learning Process]
|
||||
D1[Identify Pattern]
|
||||
D2[Validate with User]
|
||||
D3[Document in .clinerules]
|
||||
end
|
||||
|
||||
subgraph Apply [Usage]
|
||||
A1[Read .clinerules]
|
||||
A2[Apply Learned Patterns]
|
||||
A3[Improve Future Work]
|
||||
end
|
||||
|
||||
Start --> Learn
|
||||
Learn --> Apply
|
||||
```
|
||||
|
||||
### What to Capture
|
||||
- Critical implementation paths
|
||||
- User preferences and workflow
|
||||
- Project-specific patterns
|
||||
- Known challenges
|
||||
- Evolution of project decisions
|
||||
- Tool usage patterns
|
||||
|
||||
The format is flexible - focus on capturing valuable insights that help me work more effectively with you and the project. Think of .clinerules as a living document that grows smarter as we work together.
|
||||
|
||||
REMEMBER: After every memory reset, I begin completely fresh. The Memory Bank is my only link to previous work. It must be maintained with precision and clarity, as my effectiveness depends entirely on its accuracy.
|
||||
@@ -1,47 +0,0 @@
|
||||
# ميثاق المساهمين
|
||||
|
||||
## تعهدنا
|
||||
|
||||
نحن المساهمون والقائمون على هذا المشروع، نتعهد بتوفير بيئة مفتوحة ومرحبة، ونجعل المشاركة في مشروعنا ومجتمعنا تجربة خالية من التحرش للجميع، بغض النظر عن العمر، أو حجم الجسم، أو الإعاقة، أو العرق، أو الخصائص الجنسية، أو الهوية الجنسية والتعبير عنها، أو مستوى الخبرة، أو التعليم، أو الوضع الاجتماعي والاقتصادي، أو الجنسية، أو المظهر الشخصي، أو الدين، أو الهوية الجنسية والتوجه الجنسي.
|
||||
|
||||
## معاييرنا
|
||||
|
||||
أمثلة على السلوك الذي يساهم في خلق بيئة إيجابية تشمل:
|
||||
|
||||
- استخدام لغة ترحيبية وشاملة
|
||||
- احترام وجهات النظر والخبرات المختلفة
|
||||
- تقبل النقد البناء برحابة صدر
|
||||
- التركيز على ما هو الأفضل للمجتمع
|
||||
- إظهار التعاطف تجاه أعضاء المجتمع الآخرين
|
||||
|
||||
أمثلة على السلوك غير المقبول من قبل المشاركين تشمل:
|
||||
|
||||
- استخدام لغة أو صور جنسية والاهتمام الجنسي غير المرغوب فيه أو التحرش الجنسي
|
||||
- التصيد، والتعليقات المهينة/المسيئة، والهجمات الشخصية أو السياسية
|
||||
- التحرش العلني أو الخاص
|
||||
- نشر معلومات الآخرين الخاصة، مثل العنوان الفعلي أو الإلكتروني، دون إذن صريح
|
||||
- أي سلوك آخر يمكن اعتباره غير لائق في بيئة مهنية
|
||||
|
||||
## مسؤولياتنا
|
||||
|
||||
يتحمل القائمون على المشروع مسؤولية توضيح معايير السلوك المقبول، ومن المتوقع أن يتخذوا إجراءات تصحيحية مناسبة وعادلة استجابة لأي حالات سلوك غير مقبول.
|
||||
|
||||
يحق للقائمين على المشروع إزالة أو تعديل أو رفض التعليقات والالتزامات والتعليمات البرمجية وتعديلات wiki والمشكلات والمساهمات الأخرى التي لا تتماشى مع مدونة قواعد السلوك هذه، أو حظر أي مساهم بشكل مؤقت أو دائم بسبب سلوكيات أخرى يعتبرونها غير لائقة أو مهددة أو مسيئة أو ضارة، كما أنهم يتحملون مسؤولية ذلك.
|
||||
|
||||
## النطاق
|
||||
|
||||
تنطبق مدونة قواعد السلوك هذه داخل مساحات المشروع وفي الأماكن العامة عندما يمثل الفرد المشروع أو مجتمعه. تتضمن أمثلة تمثيل مشروع أو مجتمع استخدام عنوان بريد إلكتروني رسمي للمشروع، أو النشر عبر حساب رسمي على وسائل التواصل الاجتماعي، أو العمل كممثل معين في حدث عبر الإنترنت أو خارجه. يمكن للقائمين على المشروع تحديد وتوضيح تمثيل المشروع بشكل أكبر.
|
||||
|
||||
## التنفيذ
|
||||
|
||||
يمكن الإبلاغ عن حالات السلوك المسيء أو التحرش أو السلوك غير المقبول عن طريق الاتصال بفريق المشروع على hi@cline.bot. ستتم مراجعة جميع الشكاوى والتحقيق فيها وستؤدي إلى استجابة تعتبر ضرورية ومناسبة للظروف. يلتزم فريق المشروع بالحفاظ على السرية فيما يتعلق بالمبلغ عن الحادث. يمكن نشر مزيد من التفاصيل حول سياسات التنفيذ المحددة بشكل منفصل.
|
||||
|
||||
قد يواجه القائمون على المشروع الذين لا يتبعون أو يفرضون مدونة قواعد السلوك بحسن نية تداعيات مؤقتة أو دائمة على النحو الذي يحدده الأعضاء الآخرون في قيادة المشروع.
|
||||
|
||||
## الإسناد
|
||||
|
||||
تم اقتباس مدونة قواعد السلوك هذه من [تعهد المساهم][homepage]، الإصدار 1.4، متاح على https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
للحصول على إجابات للأسئلة الشائعة حول مدونة قواعد السلوك هذه، راجع https://www.contributor-covenant.org/faq
|
||||
@@ -1,93 +0,0 @@
|
||||
# المساهمة في Cline
|
||||
|
||||
نحن سعداء لاهتمامك بالمساهمة في Cline. سواء كنت تصلح خطأً أو تضيف ميزة أو تحسن الوثائق لدينا، فإن كل مساهمة تجعل Cline أذكى! للحفاظ على مجتمعنا نابضًا بالحياة وترحيبيًا، يجب على جميع الأعضاء الالتزام بـ [مدونة قواعد السلوك](CODE_OF_CONDUCT.md) لدينا.
|
||||
|
||||
## الإبلاغ عن الأخطاء أو المشكلات
|
||||
|
||||
تساعد تقارير الأخطاء على جعل Cline أفضل للجميع! قبل إنشاء مشكلة جديدة، يرجى [البحث عن المشكلات الموجودة](https://github.com/cline/cline/issues) لتجنب الازدواجية. عندما تكون جاهزًا للإبلاغ عن خطأ، انتقل إلى [صفحة المشكلات](https://github.com/cline/cline/issues/new/choose) حيث ستجد قالبًا لمساعدتك في ملء المعلومات ذات الصلة.
|
||||
|
||||
<blockquote class='warning-note'>
|
||||
🔐 <b>مهم:</b> إذا اكتشفت ثغرة أمنية، فيرجى استخدام <a href="https://github.com/cline/cline/security/advisories/new">أداة الأمان على Github للإبلاغ عنها بشكل خاص</a>.
|
||||
</blockquote>
|
||||
|
||||
## تحديد ما يجب العمل عليه
|
||||
|
||||
تبحث عن مساهمة أولى جيدة؟ تحقق من المشكلات المميزة بـ ["good first issue"](https://github.com/cline/cline/labels/good%20first%20issue) أو ["help wanted"](https://github.com/cline/cline/labels/help%20wanted). تم تحديد هذه المشكلات خصيصًا للمساهمين الجدد والمجالات التي نرحب فيها بالمساعدة!
|
||||
|
||||
نرحب أيضًا بالمساهمات في [الوثائق](https://github.com/cline/cline/tree/main/docs) لدينا! سواء كان تصحيح أخطاء إملائية، أو تحسين الأدلة الحالية، أو إنشاء محتوى تعليمي جديد - نود بناء مستودع موارد مدفوع من المجتمع يساعد الجميع على الاستفادة القصوى من Cline. يمكنك البدء بالغوص في `/docs` والبحث عن مجالات تحتاج إلى تحسين.
|
||||
|
||||
إذا كنت تخطط للعمل على ميزة أكبر، فيرجى إنشاء [طلب ميزة](https://github.com/cline/cline/discussions/categories/feature-requests?discussions_q=is%3Aopen+category%3A%22Feature+Requests%22+sort%3Atop) أولاً حتى نتمكن من مناقشة ما إذا كان ذلك يتماشى مع رؤية Cline.
|
||||
|
||||
## إعداد التطوير
|
||||
|
||||
1. **إضافات VS Code**
|
||||
|
||||
- عند فتح المشروع، سيطالبك VS Code بتثبيت الإضافات الموصى بها
|
||||
- هذه الإضافات مطلوبة للتطوير - يرجى قبول جميع مطالبات التثبيت
|
||||
- إذا تجاهلت المطالبات، يمكنك تثبيتها يدويًا من لوحة الإضافات
|
||||
|
||||
2. **التطوير المحلي**
|
||||
- قم بتشغيل `npm run install:all` لتثبيت التبعيات
|
||||
- قم بتشغيل `npm run test` لتشغيل الاختبارات محليًا
|
||||
- قبل تقديم طلب السحب، قم بتشغيل `npm run format:fix` لتنسيق التعليمات البرمجية الخاصة بك
|
||||
|
||||
## كتابة وتقديم التعليمات البرمجية
|
||||
|
||||
يمكن لأي شخص المساهمة بالتعليمات البرمجية في Cline، لكننا نطلب منك اتباع هذه الإرشادات لضمان دمج مساهماتك بسلاسة:
|
||||
|
||||
1. **احتفظ بطلبات السحب مركزة**
|
||||
|
||||
- قيد طلبات السحب بميزة واحدة أو إصلاح خطأ
|
||||
- قسم التغييرات الأكبر إلى طلبات سحب أصغر ومتصلة
|
||||
- قسم التغييرات إلى التزامات منطقية يمكن مراجعتها بشكل مستقل
|
||||
|
||||
2. **جودة التعليمات البرمجية**
|
||||
|
||||
- قم بتشغيل `npm run lint` للتحقق من نمط التعليمات البرمجية
|
||||
- قم بتشغيل `npm run format` لتنسيق التعليمات البرمجية تلقائيًا
|
||||
- يجب أن تجتاز جميع طلبات السحب عمليات التحقق المستمر التي تشمل كلاً من التنضيد والتنسيق
|
||||
- تعامل مع أي تحذيرات أو أخطاء ESLint قبل التقديم
|
||||
- اتبع أفضل ممارسات TypeScript والحفاظ على سلامة النوع
|
||||
|
||||
3. **الاختبار**
|
||||
|
||||
- أضف اختبارات للميزات الجديدة
|
||||
- قم بتشغيل `npm test` للتأكد من اجتياز جميع الاختبارات
|
||||
- قم بتحديث الاختبارات الحالية إذا كانت تغييراتك تؤثر عليها
|
||||
- تضمين كل من اختبارات الوحدة واختبارات التكامل حيثما كان ذلك مناسبًا
|
||||
|
||||
4. **إدارة الإصدار مع Changesets**
|
||||
|
||||
- أنشئ changeset لأي تغييرات واجهة المستخدم باستخدام `npm run changeset`
|
||||
- اختر زيادة الإصدار المناسبة:
|
||||
- `major` للتغييرات الكبيرة (1.0.0 → 2.0.0)
|
||||
- `minor` للميزات الجديدة (1.0.0 → 1.1.0)
|
||||
- `patch` لإصلاحات الأخطاء (1.0.0 → 1.0.1)
|
||||
- اكتب رسائل changeset واضحة ووصفية تشرح التأثير
|
||||
- لا تتطلب التغييرات في الوثائق فقط changesets
|
||||
|
||||
5. **إرشادات الالتزام (Commit Guidelines)**
|
||||
|
||||
- اكتب رسائل التزام واضحة وواصفة
|
||||
- استخدم تنسيق الالتزام التقليدي (مثل: "feat:", "fix:", "docs:")
|
||||
- أشر إلى القضايا ذات الصلة في الالتزامات باستخدام #رقم-القضية
|
||||
|
||||
6. **قبل الإرسال**
|
||||
|
||||
- قم بإعادة دمج فرعك مع أحدث إصدار من الفرع الرئيسي
|
||||
- تأكد من أن الفرع الخاص بك يُبنى بنجاح
|
||||
- تحقق من اجتياز جميع الاختبارات
|
||||
- راجع التغييرات الخاصة بك للتأكد من عدم وجود تعليمات تصحيح الأخطاء أو سجلات وحدة التحكم
|
||||
|
||||
7. **وصف طلب السحب (Pull Request Description)**
|
||||
|
||||
- صف بوضوح ما تقوم به التغييرات
|
||||
- قم بتضمين خطوات لاختبار التغييرات
|
||||
- أدرج أي تغييرات غير متوافقة
|
||||
- أضف لقطات شاشة للتغييرات في واجهة المستخدم
|
||||
|
||||
## اتفاقية المساهمة
|
||||
|
||||
من خلال إرسال طلب سحب، فإنك توافق على أن مساهماتك سيتم ترخيصها بنفس ترخيص المشروع ([Apache 2.0](LICENSE)).
|
||||
|
||||
تذكر: المساهمة في Cline لا تقتصر فقط على كتابة الكود - إنها تتعلق بأن تكون جزءًا من مجتمع يُشكل مستقبل التطوير بمساعدة الذكاء الاصطناعي. لنبنِ شيئًا رائعًا معًا! 🚀
|
||||
@@ -1,189 +0,0 @@
|
||||
<div align="center"><sub>
|
||||
العربية | <a href="https://github.com/cline/cline/blob/main/locales/es/README.md" target="_blank">الإسبانية</a> | <a href="https://github.com/cline/cline/blob/main/locales/de/README.md" target="_blank">الألمانية</a> | <a href="https://github.com/cline/cline/blob/main/locales/ja/README.md" target="_blank">اليابانية</a> | <a href="https://github.com/cline/cline/blob/main/locales/zh-cn/README.md" target="_blank">الصينية المبسطة</a> | <a href="https://github.com/cline/cline/blob/main/locales/zh-tw/README.md" target="_blank">الصينية التقليدية</a> | <a href="https://github.com/cline/cline/blob/main/locales/pt-BR/README.md" target="_blank">البرتغالية</a>
|
||||
</sub></div>
|
||||
|
||||
# Cline – \#1 على OpenRouter
|
||||
|
||||
<p align="center">
|
||||
<img src="https://media.githubusercontent.com/media/cline/cline/main/assets/docs/demo.gif" width="100%" />
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<table>
|
||||
<tbody>
|
||||
<td align="center">
|
||||
<a href="https://marketplace.visualstudio.com/items?itemName=saoudrizwan.claude-dev" target="_blank"><strong>تنزيل من متجر VS</strong></a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://discord.gg/cline" target="_blank"><strong>Discord</strong></a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://www.reddit.com/r/cline/" target="_blank"><strong>r/cline</strong></a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/cline/cline/discussions/categories/feature-requests?discussions_q=is%3Aopen+category%3A%22Feature+Requests%22+sort%3Atop" target="_blank"><strong>طلبات الميزات</strong></a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://docs.cline.bot/getting-started/getting-started-new-coders" target="_blank"><strong>البدء</strong></a>
|
||||
</td>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
التقى Cline، مساعد الذكاء الاصطناعي الذي يمكنه استخدام **سطر الأوامر** و **محرر النصوص** الخاص بك.
|
||||
|
||||
بفضل [قدرات Claude 3.5 Sonnet على التعليمات البرمجية الوكيلة](https://www-cdn.anthropic.com/fed9cc193a14b84131812372d8d5857f8f304c52/Model_Card_Claude_3_Addendum.pdf)، يمكن لـ Cline التعامل مع مهام تطوير البرامج المعقدة خطوة بخطوة. مع الأدوات التي تسمح له بإنشاء وتعديل الملفات، واستكشاف المشاريع الكبيرة، واستخدام المتصفح، وتنفيذ أوامر الطرفية (بعد منحك الإذن)، يمكنه مساعدتك بطرق تتجاوز إكمال الكود أو الدعم الفني. يمكن لـ Cline أيضًا استخدام بروتوكول سياق النموذج (MCP) لإنشاء أدوات جديدة وتوسيع قدراته الخاصة. في حين تعمل النصوص البرمجية الآلية المستقلة تقليديًا في بيئات محاصرة، توفر هذه الإضافة واجهة رسومية لموافقة المستخدم على كل تغيير في الملف وأمر طرفية، مما يوفر طريقة آمنة وسهلة الاستخدام لاستكشاف إمكانات الذكاء الاصطناعي الوكيل.
|
||||
|
||||
1. أدخل مهمتك وأضف الصور لتحويل المحاكاة إلى تطبيقات وظيفية أو إصلاح الأخطاء مع لقطات الشاشة.
|
||||
2. يبدأ Cline بتحليل هيكل الملفات الخاصة بك وشجرة التعريف المصدرية، وإجراء عمليات بحث regex، وقراءة الملفات ذات الصلة للاطلاع على المشاريع الحالية. من خلال إدارة المعلومات التي يتم إضافتها إلى السياق بعناية، يمكن لـ Cline تقديم مساعدة قيمة حتى للمشاريع الكبيرة والمعقدة دون إرهاق نافذة السياق.
|
||||
3. بمجرد حصول Cline على المعلومات التي يحتاجها، يمكنه:
|
||||
- إنشاء وتعديل الملفات + مراقبة أخطاء Linter/Compiler أثناء السير، مما يسمح له بإصلاح المشكلات مثل الواردات المفقودة وأخطاء البناء النحوي بمفرده.
|
||||
- تنفيذ الأوامر مباشرة في الطرفية الخاصة بك ومراقبة إخراجها أثناء العمل، مما يسمح له على سبيل المثال بالاستجابة لمشكلات خادم التطوير بعد تعديل ملف.
|
||||
- بالنسبة لمهام تطوير الويب، يمكن لـ Cline إطلاق الموقع في متصفح بلا رأس، والنقر، وكتابة النص، والتمرير، والتقاط لقطات الشاشة + سجلات وحدة التحكم، مما يسمح له بإصلاح أخطاء وقت التشغيل والأخطاء البصرية.
|
||||
4. عند اكتمال المهمة، سيقدم Cline النتيجة لك مع أمر طرفية مثل `open -a "Google Chrome" index.html`، والذي تقوم بتشغيله بنقرة زر.
|
||||
|
||||
> [!TIP]
|
||||
> استخدم اختصار `CMD/CTRL + Shift + P` لفتح لوحة الأوامر واكتب "Cline: Open In New Tab" لفتح الإضافة كعلامة تبويب في محرر النصوص الخاص بك. يتيح لك هذا استخدام Cline جنبًا إلى جنب مع مستكشف الملفات الخاص بك، ورؤية كيف يغير مساحة العمل الخاصة بك بوضوح أكبر.
|
||||
|
||||
---
|
||||
|
||||
<img align="right" width="340" src="https://github.com/user-attachments/assets/3cf21e04-7ce9-4d22-a7b9-ba2c595e88a4">
|
||||
|
||||
### استخدم أي واجهة برمجة تطبيقات ونموذج
|
||||
|
||||
يدعم Cline مقدمي واجهات برمجة التطبيقات مثل OpenRouter و Anthropic و OpenAI و Google Gemini و AWS Bedrock و Azure و GCP Vertex. يمكنك أيضًا تكوين أي واجهة برمجة تطبيقات متوافقة مع OpenAI، أو استخدام نموذج محلي من خلال LM Studio/Ollama. إذا كنت تستخدم OpenRouter، فستقوم الإضافة بجلب قائمة النماذج الأحدث الخاصة بهم، مما يسمح لك باستخدام أحدث النماذج بمجرد توفرها.
|
||||
|
||||
تتتبع الإضافة أيضًا إجمالي الرموز والاستخدام الخاص بواجهة برمجة التطبيقات لدورة المهمة بأكملها وطلبات فردية، مما يبقيك على اطلاع بالإنفاق في كل خطوة.
|
||||
|
||||
<!-- Transparent pixel to create line break after floating image -->
|
||||
|
||||
<img width="2000" height="0" src="https://github.com/user-attachments/assets/ee14e6f7-20b8-4391-9091-8e8e25561929"><br>
|
||||
|
||||
<img align="left" width="370" src="https://github.com/user-attachments/assets/81be79a8-1fdb-4028-9129-5fe055e01e76">
|
||||
|
||||
### تشغيل الأوامر في الطرفية
|
||||
|
||||
بفضل [تحديثات تكامل الشل الجديدة في VSCode v1.93](https://code.visualstudio.com/updates/v1_93#_terminal-shell-integration-api)، يمكن لـ Cline تنفيذ الأوامر مباشرة في الطرفية الخاصة بك وتلقي الإخراج. يسمح له هذا بأداء مجموعة واسعة من المهام، من تثبيت الحزم وتشغيل سكربتات البناء إلى نشر التطبيقات، وإدارة قواعد البيانات، وتنفيذ الاختبارات، وذلك بالتكيف مع بيئة التطوير الخاصة بك وسلسلة الأدوات للقيام بالعمل على النحو الصحيح.
|
||||
|
||||
بالنسبة للعمليات الطويلة المدى مثل خوادم التطوير، استخدم زر "المتابعة أثناء التشغيل" للسماح لـ Cline بالاستمرار في المهمة بينما يعمل الأمر في الخلفية. أثناء عمل Cline، سيتم إخباره بأي إخراج طرفية جديد على الطريق، مما يسمح له بالاستجابة للمشكلات التي قد تنشأ، مثل أخطاء وقت الإنشاء عند تعديل الملفات.
|
||||
|
||||
<!-- Transparent pixel to create line break after floating image -->
|
||||
|
||||
<img width="2000" height="0" src="https://github.com/user-attachments/assets/ee14e6f7-20b8-4391-9091-8e8e25561929"><br>
|
||||
|
||||
<img align="right" width="400" src="https://github.com/user-attachments/assets/c5977833-d9b8-491e-90f9-05f9cd38c588">
|
||||
|
||||
### إنشاء وتعديل الملفات
|
||||
|
||||
يمكن لـ Cline إنشاء وتعديل الملفات مباشرة في محرر النصوص الخاص بك، وعرض الاختلافات. يمكنك تعديل أو إلغاء تغييرات Cline مباشرة في محرر الاختلافات، أو تقديم ملاحظات في الدردشة حتى تكون راضيًا عن النتيجة. يراقب Cline أيضًا أخطاء Linter/Compiler (الواردات المفقودة، أخطاء البناء النحوي، إلخ) حتى يتمكن من إصلاح المشكلات التي تنشأ أثناء السير بمفرده.
|
||||
|
||||
يتم تسجيل جميع التغييرات التي أجراها Cline في جدول زمني للملف، مما يوفر طريقة سهلة لتتبع وإلغاء التعديلات إذا لزم الأمر.
|
||||
|
||||
<!-- Transparent pixel to create line break after floating image -->
|
||||
|
||||
<img width="2000" height="0" src="https://github.com/user-attachments/assets/ee14e6f7-20b8-4391-9091-8e8e25561929"><br>
|
||||
|
||||
<img align="left" width="370" src="https://github.com/user-attachments/assets/bc2e85ba-dfeb-4fe6-9942-7cfc4703cbe5">
|
||||
|
||||
### استخدم المتصفح
|
||||
|
||||
مع قدرة [استخدام الكمبيوتر](https://www.anthropic.com/news/3-5-models-and-computer-use) الجديدة لـ Claude 3.5 Sonnet، يمكن لـ Cline إطلاق متصفح، والنقر على العناصر، وكتابة النص، والتمرير، والتقاط لقطات الشاشة وسجلات وحدة التحكم في كل خطوة. يسمح له هذا بالتصحيح التفاعلي، واختبار نهاية إلى نهاية، وحتى الاستخدام العام للويب! يمنحه هذا الاستقلالية لإصلاح الأخطاء البصرية وأخطاء وقت التشغيل دون الحاجة إلى نسخ ولصق سجلات الأخطاء بنفسك.
|
||||
|
||||
حاول طلب من Cline "اختبار التطبيق"، وشاهده يشغل أمرًا مثل `npm run dev`، ويطلق خادم التطوير المحلي في متصفح، ويجري سلسلة من الاختبارات للتأكد من أن كل شيء يعمل. [شاهد عرضًا توضيحيًا هنا.](https://x.com/sdrzn/status/1850880547825823989)
|
||||
|
||||
<!-- Transparent pixel to create line break after floating image -->
|
||||
|
||||
<img width="2000" height="0" src="https://github.com/user-attachments/assets/ee14e6f7-20b8-4391-9091-8e8e25561929"><br>
|
||||
|
||||
<img align="right" width="350" src="https://github.com/user-attachments/assets/ac0efa14-5c1f-4c26-a42d-9d7c56f5fadd">
|
||||
|
||||
### "إضافة أداة التي..."
|
||||
|
||||
شكراً لـ [بروتوكول سياق النموذج](https://github.com/modelcontextprotocol)، يمكن لـ Cline توسيع قدراته من خلال الأدوات المخصصة. بينما يمكنك استخدام [الخوادم التي أنشأها المجتمع](https://github.com/modelcontextprotocol/servers)، يمكن لـ Cline بدلاً من ذلك إنشاء أدوات وتثبيتها مصممة خصيصًا لتناسب سير عملك. ما عليك سوى أن تطلب من Cline "إضافة أداة"، وسيتولى كل شيء، من إنشاء خادم MCP جديد إلى تثبيته في الامتداد. تصبح هذه الأدوات المخصصة بعد ذلك جزءًا من مجموعة أدوات Cline، جاهزة للاستخدام في المهام المستقبلية.
|
||||
|
||||
- **"أضف أداة تجلب تذاكر Jira"**: استرجع تذاكر AC وقم بتشغيل Cline
|
||||
- **"أضف أداة تدير AWS EC2s"**: تحقق من مقاييس الخادم وقم بتوسيع أو تقليص عدد الحالات
|
||||
- **"أضف أداة تجلب أحدث حوادث PagerDuty"**: استرجع التفاصيل واطلب من Cline إصلاح الأخطاء
|
||||
|
||||
<!-- بكسل شفاف لإنشاء فاصل سطر بعد الصورة العائمة -->
|
||||
|
||||
<img width="2000" height="0" src="https://github.com/user-attachments/assets/ee14e6f7-20b8-4391-9091-8e8e25561929"><br>
|
||||
|
||||
<img align="left" width="360" src="https://github.com/user-attachments/assets/7fdf41e6-281a-4b4b-ac19-020b838b6970">
|
||||
|
||||
### إضافة السياق
|
||||
|
||||
**`@url`**: الصق رابط URL ليقوم الامتداد بجلبه وتحويله إلى Markdown، مفيد عندما تريد تزويد Cline بأحدث الوثائق
|
||||
|
||||
**`@problems`**: أضف أخطاء وتحذيرات بيئة العمل ('لوحة المشكلات') ليتمكن Cline من إصلاحها
|
||||
|
||||
**`@file`**: يضيف محتويات ملف حتى لا تضطر إلى إهدار طلبات API بالموافقة على قراءة الملف (+ البحث في الملفات)
|
||||
|
||||
**`@folder`**: يضيف جميع ملفات المجلد دفعة واحدة لتسريع سير العمل بشكل أكبر
|
||||
|
||||
<!-- بكسل شفاف لإنشاء فاصل سطر بعد الصورة العائمة -->
|
||||
|
||||
<img width="2000" height="0" src="https://github.com/user-attachments/assets/ee14e6f7-20b8-4391-9091-8e8e25561929"><br>
|
||||
|
||||
<img align="right" width="350" src="https://github.com/user-attachments/assets/140c8606-d3bf-41b9-9a1f-4dbf0d4c90cb">
|
||||
|
||||
### نقاط التحقق: المقارنة والاستعادة
|
||||
|
||||
أثناء عمل Cline على مهمة، يأخذ الامتداد لقطة من بيئة العمل في كل خطوة. يمكنك استخدام زر "Compare" لرؤية الفرق بين اللقطة وبيئة العمل الحالية، وزر "Restore" للعودة إلى تلك النقطة.
|
||||
|
||||
على سبيل المثال، عند العمل مع خادم ويب محلي، يمكنك استخدام "استعادة بيئة العمل فقط" لاختبار إصدارات مختلفة من تطبيقك بسرعة، ثم استخدام "استعادة المهمة وبيئة العمل" عندما تجد الإصدار الذي تريد المتابعة منه. يتيح لك ذلك استكشاف أساليب مختلفة بأمان دون فقدان التقدم.
|
||||
|
||||
<!-- بكسل شفاف لإنشاء فاصل سطر بعد الصورة العائمة -->
|
||||
|
||||
<img width="2000" height="0" src="https://github.com/user-attachments/assets/ee14e6f7-20b8-4391-9091-8e8e25561929"><br>
|
||||
|
||||
## المساهمة
|
||||
|
||||
للمساهمة في المشروع، ابدأ بـ [دليل المساهمة](CONTRIBUTING.md) لتعلم الأساسيات. يمكنك أيضًا الانضمام إلى [خادم Discord](https://discord.gg/cline) للدردشة مع المساهمين الآخرين في قناة `#contributors`. إذا كنت تبحث عن عمل بدوام كامل، تحقق من الوظائف المتاحة على [صفحة التوظيف](https://cline.bot/join-us)!
|
||||
|
||||
<details>
|
||||
<summary>تعليمات التطوير المحلي</summary>
|
||||
|
||||
1. استنساخ المستودع _(يتطلب [git-lfs](https://git-lfs.com/))_:
|
||||
```bash
|
||||
git clone https://github.com/cline/cline.git
|
||||
```
|
||||
2. افتح المشروع في VSCode:
|
||||
```bash
|
||||
code cline
|
||||
```
|
||||
3. قم بتثبيت التبعيات اللازمة للامتداد وواجهة الويب:
|
||||
```bash
|
||||
npm run install:all
|
||||
```
|
||||
4. قم بالتشغيل بالضغط على `F5` (أو من `Run` -> `Start Debugging`) لفتح نافذة VSCode جديدة مع تحميل الامتداد. (قد تحتاج إلى تثبيت [إضافة esbuild problem matchers](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers) إذا واجهت مشكلات في بناء المشروع.)
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>إنشاء طلب سحب (Pull Request)</summary>
|
||||
|
||||
1. قبل إنشاء PR، قم بإنشاء إدخال للتغييرات:
|
||||
```bash
|
||||
npm run changeset
|
||||
```
|
||||
سيطلب منك تحديد:
|
||||
- نوع التغيير (رئيسي، ثانوي، إصلاح)
|
||||
- `رئيسي` → تغييرات غير متوافقة (1.0.0 → 2.0.0)
|
||||
- `ثانوي` → ميزات جديدة (1.0.0 → 1.1.0)
|
||||
- `إصلاح` → إصلاحات للأخطاء (1.0.0 → 1.0.1)
|
||||
- وصف التغييرات التي قمت بها
|
||||
|
||||
2. قم بحفظ التغييرات وملف `.changeset` الذي تم إنشاؤه
|
||||
|
||||
3. ادفع فرعك وأنشئ PR على GitHub. سيقوم CI بـ:
|
||||
- تشغيل الاختبارات والفحوصات
|
||||
- سيقوم Changesetbot بإنشاء تعليق يوضح تأثير الإصدار
|
||||
- عند الدمج مع الفرع الرئيسي، سيقوم Changesetbot بإنشاء PR لحزم الإصدار
|
||||
- عند دمج PR لحزم الإصدار، سيتم نشر إصدار جديد
|
||||
|
||||
</details>
|
||||
|
||||
## الرخصة
|
||||
|
||||
[Apache 2.0 © 2025 Cline Bot Inc.](./LICENSE)
|
||||
Generated
+15
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "claude-dev",
|
||||
"version": "3.4.3",
|
||||
"version": "3.4.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "claude-dev",
|
||||
"version": "3.4.3",
|
||||
"version": "3.4.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/bedrock-sdk": "^0.10.2",
|
||||
@@ -40,6 +40,7 @@
|
||||
"os-name": "^6.0.0",
|
||||
"p-wait-for": "^5.0.2",
|
||||
"pdf-parse": "^1.1.1",
|
||||
"posthog-node": "^4.6.0",
|
||||
"puppeteer-chromium-resolver": "^23.0.0",
|
||||
"puppeteer-core": "^23.4.0",
|
||||
"serialize-error": "^11.0.3",
|
||||
@@ -11606,6 +11607,18 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/posthog-node": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-4.6.0.tgz",
|
||||
"integrity": "sha512-Dq6sBTeBXfK0Ez9OWAl+lhUcSInGUshxtoe0b26gFZtnV4byWTEZtwitKB6Xel9wYx3pNHixKOTSIHC/6a6CIA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.7.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
|
||||
+6
-5
@@ -2,7 +2,7 @@
|
||||
"name": "claude-dev",
|
||||
"displayName": "Cline",
|
||||
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
|
||||
"version": "3.4.6",
|
||||
"version": "3.4.0",
|
||||
"icon": "assets/icons/icon.png",
|
||||
"galleryBanner": {
|
||||
"color": "#617A91",
|
||||
@@ -73,7 +73,7 @@
|
||||
{
|
||||
"command": "cline.mcpButtonClicked",
|
||||
"title": "MCP Servers",
|
||||
"icon": "$(extensions)"
|
||||
"icon": "$(server)"
|
||||
},
|
||||
{
|
||||
"command": "cline.historyButtonClicked",
|
||||
@@ -182,10 +182,10 @@
|
||||
"default": null,
|
||||
"description": "Path to Chrome executable for browser use functionality. If not set, the extension will attempt to find or download it automatically."
|
||||
},
|
||||
"cline.mcpMarketplace.enabled": {
|
||||
"cline.enableTelemetry": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Controls whether the MCP Marketplace is enabled."
|
||||
"default": false,
|
||||
"description": "Enables extension to post anonymous error tracking & usage data to help improve the product."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,6 +268,7 @@
|
||||
"os-name": "^6.0.0",
|
||||
"p-wait-for": "^5.0.2",
|
||||
"pdf-parse": "^1.1.1",
|
||||
"posthog-node": "^4.6.0",
|
||||
"puppeteer-chromium-resolver": "^23.0.0",
|
||||
"puppeteer-core": "^23.4.0",
|
||||
"serialize-error": "^11.0.3",
|
||||
|
||||
@@ -24,7 +24,6 @@ export class AnthropicHandler implements ApiHandler {
|
||||
const modelId = model.id
|
||||
switch (modelId) {
|
||||
// 'latest' alias does not support cache_control
|
||||
case "claude-3-7-sonnet-20250219":
|
||||
case "claude-3-5-sonnet-20241022":
|
||||
case "claude-3-5-haiku-20241022":
|
||||
case "claude-3-opus-20240229":
|
||||
@@ -89,7 +88,6 @@ export class AnthropicHandler implements ApiHandler {
|
||||
// https://github.com/anthropics/anthropic-sdk-typescript?tab=readme-ov-file#default-headers
|
||||
// https://github.com/anthropics/anthropic-sdk-typescript/commit/c920b77fc67bd839bfeb6716ceab9d7c9bbe7393
|
||||
switch (modelId) {
|
||||
case "claude-3-7-sonnet-20250219":
|
||||
case "claude-3-5-sonnet-20241022":
|
||||
case "claude-3-5-haiku-20241022":
|
||||
case "claude-3-opus-20240229":
|
||||
|
||||
@@ -3,25 +3,79 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import { ApiHandler } from "../"
|
||||
import { ApiHandlerOptions, bedrockDefaultModelId, BedrockModelId, bedrockModels, ModelInfo } from "../../shared/api"
|
||||
import { ApiStream } from "../transform/stream"
|
||||
import { fromNodeProviderChain } from "@aws-sdk/credential-providers"
|
||||
import { fromIni } from "@aws-sdk/credential-providers"
|
||||
|
||||
// https://docs.anthropic.com/en/api/claude-on-amazon-bedrock
|
||||
export class AwsBedrockHandler implements ApiHandler {
|
||||
private options: ApiHandlerOptions
|
||||
private client: AnthropicBedrock | any
|
||||
private initializationPromise: Promise<void>
|
||||
|
||||
constructor(options: ApiHandlerOptions) {
|
||||
this.options = options
|
||||
this.initializationPromise = this.initializeClient()
|
||||
}
|
||||
|
||||
private async initializeClient() {
|
||||
let clientConfig: any = {
|
||||
awsRegion: this.options.awsRegion || "us-east-1",
|
||||
}
|
||||
try {
|
||||
if (this.options.awsUseProfile) {
|
||||
// Use profile-based credentials if enabled
|
||||
// Use named profile, defaulting to 'default' if not specified
|
||||
var credentials: any
|
||||
if (this.options.awsProfile) {
|
||||
credentials = await fromIni({
|
||||
profile: this.options.awsProfile,
|
||||
ignoreCache: true,
|
||||
})()
|
||||
} else {
|
||||
credentials = await fromIni({
|
||||
ignoreCache: true,
|
||||
})()
|
||||
}
|
||||
clientConfig.awsAccessKey = credentials.accessKeyId
|
||||
clientConfig.awsSecretKey = credentials.secretAccessKey
|
||||
clientConfig.awsSessionToken = credentials.sessionToken
|
||||
} else if (this.options.awsAccessKey && this.options.awsSecretKey) {
|
||||
// Use direct credentials if provided
|
||||
clientConfig.awsAccessKey = this.options.awsAccessKey
|
||||
clientConfig.awsSecretKey = this.options.awsSecretKey
|
||||
if (this.options.awsSessionToken) {
|
||||
clientConfig.awsSessionToken = this.options.awsSessionToken
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize Bedrock client:", error)
|
||||
throw error
|
||||
} finally {
|
||||
this.client = new AnthropicBedrock(clientConfig)
|
||||
}
|
||||
}
|
||||
|
||||
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
// cross region inference requires prefixing the model id with the region
|
||||
let modelId = await this.getModelId()
|
||||
let modelId: string
|
||||
if (this.options.awsUseCrossRegionInference) {
|
||||
let regionPrefix = (this.options.awsRegion || "").slice(0, 3)
|
||||
switch (regionPrefix) {
|
||||
case "us-":
|
||||
modelId = `us.${this.getModel().id}`
|
||||
break
|
||||
case "eu-":
|
||||
modelId = `eu.${this.getModel().id}`
|
||||
break
|
||||
default:
|
||||
// cross region inference is not supported in this region, falling back to default model
|
||||
modelId = this.getModel().id
|
||||
break
|
||||
}
|
||||
} else {
|
||||
modelId = this.getModel().id
|
||||
}
|
||||
|
||||
// create anthropic client, using sessions created or renewed after this handler's
|
||||
// initialization, and allowing for session renewal if necessary as well
|
||||
let client = await this.getClient()
|
||||
|
||||
const stream = await client.messages.create({
|
||||
const stream = await this.client.messages.create({
|
||||
model: modelId,
|
||||
max_tokens: this.getModel().info.maxTokens || 8192,
|
||||
temperature: 0,
|
||||
@@ -88,68 +142,4 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
info: bedrockModels[bedrockDefaultModelId],
|
||||
}
|
||||
}
|
||||
|
||||
private async getClient(): Promise<AnthropicBedrock> {
|
||||
// Create AWS credentials by executing a an AWS provider chain exactly as the
|
||||
// Anthropic SDK does it, by wrapping the default chain into a temporary process
|
||||
// environment.
|
||||
const providerChain = fromNodeProviderChain()
|
||||
const credentials = await AwsBedrockHandler.withTempEnv(
|
||||
() => {
|
||||
AwsBedrockHandler.setEnv("AWS_REGION", this.options.awsRegion)
|
||||
AwsBedrockHandler.setEnv("AWS_ACCESS_KEY_ID", this.options.awsAccessKey)
|
||||
AwsBedrockHandler.setEnv("AWS_SECRET_ACCESS_KEY", this.options.awsSecretKey)
|
||||
AwsBedrockHandler.setEnv("AWS_SESSION_TOKEN", this.options.awsSessionToken)
|
||||
AwsBedrockHandler.setEnv("AWS_PROFILE", this.options.awsProfile)
|
||||
},
|
||||
() => providerChain(),
|
||||
)
|
||||
|
||||
// Return an AnthropicBedrock client with the resolved/assumed credentials.
|
||||
//
|
||||
// When AnthropicBedrock creates its AWS client, the chain will execute very
|
||||
// fast as the access/secret keys will already be already provided, and have
|
||||
// a higher precedence than the profiles.
|
||||
return new AnthropicBedrock({
|
||||
awsAccessKey: credentials.accessKeyId,
|
||||
awsSecretKey: credentials.secretAccessKey,
|
||||
awsSessionToken: credentials.sessionToken,
|
||||
awsRegion: this.options.awsRegion || "us-east-1",
|
||||
})
|
||||
}
|
||||
|
||||
private async getModelId(): Promise<string> {
|
||||
if (this.options.awsUseCrossRegionInference) {
|
||||
let regionPrefix = (this.options.awsRegion || "").slice(0, 3)
|
||||
switch (regionPrefix) {
|
||||
case "us-":
|
||||
return `us.${this.getModel().id}`
|
||||
case "eu-":
|
||||
return `eu.${this.getModel().id}`
|
||||
break
|
||||
default:
|
||||
// cross region inference is not supported in this region, falling back to default model
|
||||
return this.getModel().id
|
||||
break
|
||||
}
|
||||
}
|
||||
return this.getModel().id
|
||||
}
|
||||
|
||||
private static async withTempEnv<R>(updateEnv: () => void, fn: () => Promise<R>): Promise<R> {
|
||||
const previousEnv = { ...process.env }
|
||||
|
||||
try {
|
||||
updateEnv()
|
||||
return await fn()
|
||||
} finally {
|
||||
process.env = previousEnv
|
||||
}
|
||||
}
|
||||
|
||||
private static async setEnv(key: string, value: string | undefined) {
|
||||
if (key !== "" && value !== undefined) {
|
||||
process.env[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,6 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
// prompt caching: https://openrouter.ai/docs/prompt-caching
|
||||
// this is specifically for claude models (some models may 'support prompt caching' automatically without this)
|
||||
switch (model.id) {
|
||||
case "anthropic/claude-3-7-sonnet":
|
||||
case "anthropic/claude-3-7-sonnet:beta":
|
||||
case "anthropic/claude-3.5-sonnet":
|
||||
case "anthropic/claude-3.5-sonnet:beta":
|
||||
case "anthropic/claude-3.5-sonnet-20240620":
|
||||
@@ -91,8 +89,6 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
// (models usually default to max tokens allowed)
|
||||
let maxTokens: number | undefined
|
||||
switch (model.id) {
|
||||
case "anthropic/claude-3-7-sonnet":
|
||||
case "anthropic/claude-3-7-sonnet:beta":
|
||||
case "anthropic/claude-3.5-sonnet":
|
||||
case "anthropic/claude-3.5-sonnet:beta":
|
||||
case "anthropic/claude-3.5-sonnet-20240620":
|
||||
@@ -107,11 +103,14 @@ export class OpenRouterHandler implements ApiHandler {
|
||||
|
||||
let temperature = 0
|
||||
let topP: number | undefined = undefined
|
||||
// Handle models based on deepseek-r1
|
||||
if (this.getModel().id.startsWith("deepseek/deepseek-r1") || this.getModel().id === "perplexity/sonar-reasoning") {
|
||||
// Recommended values from DeepSeek
|
||||
temperature = 0.7
|
||||
topP = 0.95
|
||||
// Recommended temperature for DeepSeek reasoning models
|
||||
temperature = 0.6
|
||||
// DeepSeek highly recommends using user instead of system role
|
||||
openAiMessages = convertToR1Format([{ role: "user", content: systemPrompt }, ...messages])
|
||||
// Some provider support topP and 0.95 is value that Deepseek used in their benchmarks
|
||||
topP = 0.95
|
||||
}
|
||||
|
||||
// Removes messages in the middle when close to context window limit. Should not be applied to models that support prompt caching since it would continuously break the cache.
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import OpenAI from "openai"
|
||||
|
||||
type ContentPartText = OpenAI.Chat.ChatCompletionContentPartText
|
||||
type ContentPartImage = OpenAI.Chat.ChatCompletionContentPartImage
|
||||
type UserMessage = OpenAI.Chat.ChatCompletionUserMessageParam
|
||||
type AssistantMessage = OpenAI.Chat.ChatCompletionAssistantMessageParam
|
||||
type Message = OpenAI.Chat.ChatCompletionMessageParam
|
||||
type AnthropicMessage = Anthropic.Messages.MessageParam
|
||||
|
||||
/**
|
||||
* Converts Anthropic messages to OpenAI format and merges consecutive messages with the same role.
|
||||
* Converts Anthropic messages to OpenAI format while merging consecutive messages with the same role.
|
||||
* This is required for DeepSeek Reasoner which does not support successive messages with the same role.
|
||||
* DeepSeek highly recommends using 'user' role instead of 'system' role for optimal performance.
|
||||
*
|
||||
* @param messages Array of Anthropic messages
|
||||
* @returns Array of OpenAI messages where consecutive messages with the same role are merged together
|
||||
* @returns Array of OpenAI messages where consecutive messages with the same role are combined
|
||||
*/
|
||||
export function convertToR1Format(messages: Anthropic.Messages.MessageParam[]): OpenAI.Chat.ChatCompletionMessageParam[] {
|
||||
return messages.reduce<OpenAI.Chat.ChatCompletionMessageParam[]>((merged, message) => {
|
||||
export function convertToR1Format(messages: AnthropicMessage[]): Message[] {
|
||||
return messages.reduce<Message[]>((merged, message) => {
|
||||
const lastMessage = merged[merged.length - 1]
|
||||
let messageContent: string | (OpenAI.Chat.ChatCompletionContentPartText | OpenAI.Chat.ChatCompletionContentPartImage)[] =
|
||||
""
|
||||
let messageContent: string | (ContentPartText | ContentPartImage)[] = ""
|
||||
let hasImages = false
|
||||
|
||||
// Convert content to appropriate format
|
||||
if (Array.isArray(message.content)) {
|
||||
const textParts: string[] = []
|
||||
const imageParts: OpenAI.Chat.ChatCompletionContentPartImage[] = []
|
||||
const imageParts: ContentPartImage[] = []
|
||||
|
||||
message.content.forEach((part) => {
|
||||
if (part.type === "text") {
|
||||
@@ -34,7 +40,7 @@ export function convertToR1Format(messages: Anthropic.Messages.MessageParam[]):
|
||||
})
|
||||
|
||||
if (hasImages) {
|
||||
const parts: (OpenAI.Chat.ChatCompletionContentPartText | OpenAI.Chat.ChatCompletionContentPartImage)[] = []
|
||||
const parts: (ContentPartText | ContentPartImage)[] = []
|
||||
if (textParts.length > 0) {
|
||||
parts.push({ type: "text", text: textParts.join("\n") })
|
||||
}
|
||||
@@ -47,11 +53,13 @@ export function convertToR1Format(messages: Anthropic.Messages.MessageParam[]):
|
||||
messageContent = message.content
|
||||
}
|
||||
|
||||
// If the last message has the same role, merge the content
|
||||
// If last message has same role, merge the content
|
||||
if (lastMessage?.role === message.role) {
|
||||
if (typeof lastMessage.content === "string" && typeof messageContent === "string") {
|
||||
lastMessage.content += `\n${messageContent}`
|
||||
} else {
|
||||
}
|
||||
// If either has image content, convert both to array format
|
||||
else {
|
||||
const lastContent = Array.isArray(lastMessage.content)
|
||||
? lastMessage.content
|
||||
: [{ type: "text" as const, text: lastMessage.content || "" }]
|
||||
@@ -61,32 +69,30 @@ export function convertToR1Format(messages: Anthropic.Messages.MessageParam[]):
|
||||
: [{ type: "text" as const, text: messageContent }]
|
||||
|
||||
if (message.role === "assistant") {
|
||||
const mergedContent = [
|
||||
...lastContent,
|
||||
...newContent,
|
||||
] as OpenAI.Chat.ChatCompletionAssistantMessageParam["content"]
|
||||
const mergedContent = [...lastContent, ...newContent] as AssistantMessage["content"]
|
||||
lastMessage.content = mergedContent
|
||||
} else {
|
||||
const mergedContent = [...lastContent, ...newContent] as OpenAI.Chat.ChatCompletionUserMessageParam["content"]
|
||||
const mergedContent = [...lastContent, ...newContent] as UserMessage["content"]
|
||||
lastMessage.content = mergedContent
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Adds new message with the correct type based on role
|
||||
// Add as new message with the correct type based on role
|
||||
if (message.role === "assistant") {
|
||||
const newMessage: OpenAI.Chat.ChatCompletionAssistantMessageParam = {
|
||||
const newMessage: AssistantMessage = {
|
||||
role: "assistant",
|
||||
content: messageContent as OpenAI.Chat.ChatCompletionAssistantMessageParam["content"],
|
||||
content: messageContent as AssistantMessage["content"],
|
||||
}
|
||||
merged.push(newMessage)
|
||||
} else {
|
||||
const newMessage: OpenAI.Chat.ChatCompletionUserMessageParam = {
|
||||
const newMessage: UserMessage = {
|
||||
role: "user",
|
||||
content: messageContent as OpenAI.Chat.ChatCompletionUserMessageParam["content"],
|
||||
content: messageContent as UserMessage["content"],
|
||||
}
|
||||
merged.push(newMessage)
|
||||
}
|
||||
}
|
||||
|
||||
return merged
|
||||
}, [])
|
||||
}
|
||||
|
||||
+41
-10
@@ -59,6 +59,7 @@ import { formatResponse } from "./prompts/responses"
|
||||
import { addUserInstructions, SYSTEM_PROMPT } from "./prompts/system"
|
||||
import { getNextTruncationRange, getTruncatedMessages } from "./sliding-window"
|
||||
import { ClineProvider, GlobalFileNames } from "./webview/ClineProvider"
|
||||
import posthog from "../services/analytics/PostHogClient"
|
||||
|
||||
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution
|
||||
|
||||
@@ -148,6 +149,16 @@ export class Cline {
|
||||
} else {
|
||||
throw new Error("Either historyItem or task/images must be provided")
|
||||
}
|
||||
// capture start of thread with the state at the beginning
|
||||
posthog.capture({
|
||||
event: "cline created",
|
||||
properties: {
|
||||
taskId: this.taskId,
|
||||
isHistory: !!historyItem,
|
||||
chatMode: this.chatSettings.mode,
|
||||
hasImages: !!images,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
updateBrowserSettings(browserSettings: BrowserSettings) {
|
||||
@@ -353,17 +364,15 @@ export class Cline {
|
||||
break
|
||||
}
|
||||
|
||||
if (restoreType !== "task") {
|
||||
// Set isCheckpointCheckedOut flag on the message
|
||||
// Find all checkpoint messages before this one
|
||||
const checkpointMessages = this.clineMessages.filter((m) => m.say === "checkpoint_created")
|
||||
const currentMessageIndex = checkpointMessages.findIndex((m) => m.ts === messageTs)
|
||||
// Set isCheckpointCheckedOut flag on the message
|
||||
// Find all checkpoint messages before this one
|
||||
const checkpointMessages = this.clineMessages.filter((m) => m.say === "checkpoint_created")
|
||||
const currentMessageIndex = checkpointMessages.findIndex((m) => m.ts === messageTs)
|
||||
|
||||
// Set isCheckpointCheckedOut to false for all checkpoint messages
|
||||
checkpointMessages.forEach((m, i) => {
|
||||
m.isCheckpointCheckedOut = i === currentMessageIndex
|
||||
})
|
||||
}
|
||||
// Set isCheckpointCheckedOut to false for all checkpoint messages
|
||||
checkpointMessages.forEach((m, i) => {
|
||||
m.isCheckpointCheckedOut = i === currentMessageIndex
|
||||
})
|
||||
|
||||
await this.saveClineMessages()
|
||||
|
||||
@@ -3139,6 +3148,7 @@ export class Cline {
|
||||
try {
|
||||
for await (const chunk of stream) {
|
||||
if (!chunk) {
|
||||
// Sometimes chunk is undefined, no idea that can cause it, but this workaround seems to fix it
|
||||
continue
|
||||
}
|
||||
switch (chunk.type) {
|
||||
@@ -3265,6 +3275,27 @@ export class Cline {
|
||||
this.consecutiveMistakeCount++
|
||||
}
|
||||
|
||||
posthog.capture({
|
||||
event: "message sent",
|
||||
properties: {
|
||||
taskId: this.taskId,
|
||||
chatMode: this.chatSettings.mode,
|
||||
apiConversationHistoryCount: this.apiConversationHistory.length,
|
||||
userMessageCount: this.apiConversationHistory.filter((m) => m.role === "user").length,
|
||||
assistantMessageCount: this.apiConversationHistory.filter((m) => m.role === "assistant").length,
|
||||
clineMessageCount: this.clineMessages.length,
|
||||
textMessageCount: this.clineMessages.filter((m) => !!m.text).length,
|
||||
askMessageCount: this.clineMessages.filter((m) => !!m.ask).length,
|
||||
sayMessageCount: this.clineMessages.filter((m) => !!m.say).length,
|
||||
reasoningMessageCount: this.clineMessages.filter((m) => !!m.reasoning).length,
|
||||
partialMessageCount: this.clineMessages.filter((m) => !!m.partial).length,
|
||||
consecutiveMistakeCount: this.consecutiveMistakeCount,
|
||||
consecutiveAutoApprovedRequestsCount: this.consecutiveAutoApprovedRequestsCount,
|
||||
toolUseCount: this.clineMessages.filter((m) => m.say === "tool").length,
|
||||
checkpointsCount: this.clineMessages.filter((m) => m.say === "checkpoint_created").length,
|
||||
},
|
||||
})
|
||||
|
||||
const recDidEndLoop = await this.recursivelyMakeClineRequests(this.userMessageContent)
|
||||
didEndLoop = recDidEndLoop
|
||||
} else {
|
||||
|
||||
@@ -331,24 +331,7 @@ ${
|
||||
<access_mcp_resource>
|
||||
<server_name>weather-server</server_name>
|
||||
<uri>weather://san-francisco/current</uri>
|
||||
</access_mcp_resource>
|
||||
|
||||
## Example 6: Another example of using an MCP tool (where the server name is a unique identifier such as a URL)
|
||||
|
||||
<use_mcp_tool>
|
||||
<server_name>github.com/modelcontextprotocol/servers/tree/main/src/github</server_name>
|
||||
<tool_name>create_issue</tool_name>
|
||||
<arguments>
|
||||
{
|
||||
"owner": "octocat",
|
||||
"repo": "hello-world",
|
||||
"title": "Found a bug",
|
||||
"body": "I'm having a problem with this.",
|
||||
"labels": ["bug", "help wanted"],
|
||||
"assignees": ["octocat"]
|
||||
}
|
||||
</arguments>
|
||||
</use_mcp_tool>`
|
||||
</access_mcp_resource>`
|
||||
: ""
|
||||
}
|
||||
|
||||
@@ -759,16 +742,15 @@ IMPORTANT: Regardless of what else you see in the MCP settings file, you must de
|
||||
"args": ["/path/to/weather-server/build/index.js"],
|
||||
"env": {
|
||||
"OPENWEATHER_API_KEY": "user-provided-api-key"
|
||||
},
|
||||
"timeout": 60, // 60 seconds should be set as the default timeout for all MCP servers
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
(Note: the user may also ask you to install the MCP server to the Claude desktop app, in which case you would read then modify \`~/Library/Application\ Support/Claude/claude_desktop_config.json\` on macOS for example. It follows the same format of a top level \`mcpServers\` object.)
|
||||
|
||||
6. After you have edited the MCP settings configuration file, the system will automatically run all the servers and expose the available tools and resources in the 'Connected MCP Servers' section. (Note: If you encounter a 'not connected' error when testing a newly installed mcp server, a common cause is an incorrect build path in your MCP settings configuration. Since compiled JavaScript files are commonly output to either 'dist/' or 'build/' directories, double-check that the build path in your MCP settings matches where your files are actually being compiled. E.g. If you assumed 'build' as the folder, check tsconfig.json to see if it's using 'dist' instead.)
|
||||
6. After you have edited the MCP settings configuration file, the system will automatically run all the servers and expose the available tools and resources in the 'Connected MCP Servers' section.
|
||||
|
||||
7. Now that you have access to these new tools and resources, you may suggest ways the user can command you to invoke them - for example, with this new weather tool now available, you can invite the user to ask "what's the weather in San Francisco?"
|
||||
|
||||
@@ -883,10 +865,9 @@ In each user message, the environment_details will specify the current mode. The
|
||||
## What is PLAN MODE?
|
||||
|
||||
- While you are usually in ACT MODE, the user may switch to PLAN MODE in order to have a back and forth with you to plan how to best accomplish the task.
|
||||
- When starting in PLAN MODE, depending on the user's request, you may need to do some information gathering e.g. using read_file or search_files to get more context about the task. You may also ask the user clarifying questions to get a better understanding of the task. You may return mermaid diagrams to visually display your understanding.
|
||||
- Once you've gained more context about the user's request, you should architect a detailed plan for how you will accomplish the task. Returning mermaid diagrams may be helpful here as well.
|
||||
- When starting in PLAN MODE, depending on the user's request, you may need to do some information gathering e.g. using read_file or search_files to get more context about the task. You may also ask the user clarifying questions to get a better understanding of the task.
|
||||
- Once you've gained more context about the user's request, you should architect a detailed plan for how you will accomplish the task.
|
||||
- Then you might ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and plan the best way to accomplish it.
|
||||
- If at any point a mermaid diagram would make your plan clearer to help the user quickly see the structure, you are encouraged to include a Mermaid code block in the response. (Note: if you use colors in your mermaid diagrams, be sure to use high contrast colors so the text is readable.)
|
||||
- Finally once it seems like you've reached a good plan, ask the user to switch you back to ACT MODE to implement the solution.
|
||||
|
||||
====
|
||||
|
||||
+115
-391
@@ -14,7 +14,6 @@ import { selectImages } from "../../integrations/misc/process-images"
|
||||
import { getTheme } from "../../integrations/theme/getTheme"
|
||||
import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
|
||||
import { McpHub } from "../../services/mcp/McpHub"
|
||||
import { McpDownloadResponse, McpMarketplaceCatalog, McpMarketplaceItem, McpServer } from "../../shared/mcp"
|
||||
import { FirebaseAuthManager, UserInfo } from "../../services/auth/FirebaseAuthManager"
|
||||
import { ApiProvider, ModelInfo } from "../../shared/api"
|
||||
import { findLast } from "../../shared/array"
|
||||
@@ -29,10 +28,7 @@ import { getUri } from "./getUri"
|
||||
import { AutoApprovalSettings, DEFAULT_AUTO_APPROVAL_SETTINGS } from "../../shared/AutoApprovalSettings"
|
||||
import { BrowserSettings, DEFAULT_BROWSER_SETTINGS } from "../../shared/BrowserSettings"
|
||||
import { ChatSettings, DEFAULT_CHAT_SETTINGS } from "../../shared/ChatSettings"
|
||||
import { DIFF_VIEW_URI_SCHEME } from "../../integrations/editor/DiffViewProvider"
|
||||
import { searchCommits } from "../../utils/git"
|
||||
import { ChatContent } from "../../shared/ChatContent"
|
||||
import { getShell } from "../../utils/shell"
|
||||
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
@@ -93,8 +89,7 @@ type GlobalStateKey =
|
||||
| "qwenApiLine"
|
||||
| "requestyModelId"
|
||||
| "togetherModelId"
|
||||
| "mcpMarketplaceCatalog"
|
||||
|
||||
| "hideTelemetryOptIn"
|
||||
export const GlobalFileNames = {
|
||||
apiConversationHistory: "api_conversation_history.json",
|
||||
uiMessages: "ui_messages.json",
|
||||
@@ -113,7 +108,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
workspaceTracker?: WorkspaceTracker
|
||||
mcpHub?: McpHub
|
||||
private authManager: FirebaseAuthManager
|
||||
private latestAnnouncementId = "feb-19-2025" // update to some unique identifier when we add a new announcement
|
||||
private latestAnnouncementId = "jan-20-2025" // update to some unique identifier when we add a new announcement
|
||||
|
||||
constructor(
|
||||
readonly context: vscode.ExtensionContext,
|
||||
@@ -241,7 +236,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
this.disposables,
|
||||
)
|
||||
|
||||
// Listen for configuration changes
|
||||
// Listen for when color changes
|
||||
vscode.workspace.onDidChangeConfiguration(
|
||||
async (e) => {
|
||||
if (e && e.affectsConfiguration("workbench.colorTheme")) {
|
||||
@@ -251,10 +246,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
text: JSON.stringify(await getTheme()),
|
||||
})
|
||||
}
|
||||
if (e && e.affectsConfiguration("cline.mcpMarketplace.enabled")) {
|
||||
// Update state when marketplace tab setting changes
|
||||
await this.postStateToWebview()
|
||||
}
|
||||
},
|
||||
null,
|
||||
this.disposables,
|
||||
@@ -365,7 +356,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${webview.cspSource}; style-src ${webview.cspSource} 'unsafe-inline'; img-src ${webview.cspSource} https: data:; script-src 'nonce-${nonce}';">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; connect-src https://*.posthog.com; font-src ${webview.cspSource}; style-src ${webview.cspSource} 'unsafe-inline'; img-src ${webview.cspSource} https: data:; script-src 'nonce-${nonce}' https://*.posthog.com;">
|
||||
<link rel="stylesheet" type="text/css" href="${stylesUri}">
|
||||
<link href="${codiconsUri}" rel="stylesheet" />
|
||||
<title>Cline</title>
|
||||
@@ -410,17 +401,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
// gui relies on model info to be up-to-date to provide the most accurate pricing, so we need to fetch the latest details on launch.
|
||||
// we do this for all users since many users switch between api providers and if they were to switch back to openrouter it would be showing outdated model info if we hadn't retrieved the latest at this point
|
||||
// (see normalizeApiConfiguration > openrouter)
|
||||
// Prefetch marketplace and OpenRouter models
|
||||
|
||||
this.getGlobalState("mcpMarketplaceCatalog").then((mcpMarketplaceCatalog) => {
|
||||
if (mcpMarketplaceCatalog) {
|
||||
this.postMessageToWebview({
|
||||
type: "mcpMarketplaceCatalog",
|
||||
mcpMarketplaceCatalog: mcpMarketplaceCatalog as McpMarketplaceCatalog,
|
||||
})
|
||||
}
|
||||
})
|
||||
this.silentlyRefreshMcpMarketplace()
|
||||
this.refreshOpenRouterModels().then(async (openRouterModels) => {
|
||||
if (openRouterModels) {
|
||||
// update model info in state (this needs to be done here since we don't want to update state while settings is open, and we may refresh models there)
|
||||
@@ -434,7 +414,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
break
|
||||
case "newTask":
|
||||
// Code that should run in response to the hello message command
|
||||
@@ -556,9 +535,105 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
await this.postStateToWebview()
|
||||
}
|
||||
break
|
||||
case "togglePlanActMode":
|
||||
case "chatSettings":
|
||||
if (message.chatSettings) {
|
||||
await this.togglePlanActModeWithChatSettings(message.chatSettings, message.chatContent)
|
||||
const didSwitchToActMode = message.chatSettings.mode === "act"
|
||||
|
||||
// Get previous model info that we will revert to after saving current mode api info
|
||||
const {
|
||||
apiConfiguration,
|
||||
previousModeApiProvider: newApiProvider,
|
||||
previousModeModelId: newModelId,
|
||||
previousModeModelInfo: newModelInfo,
|
||||
} = await this.getState()
|
||||
|
||||
// Save the last model used in this mode
|
||||
await this.updateGlobalState("previousModeApiProvider", apiConfiguration.apiProvider)
|
||||
switch (apiConfiguration.apiProvider) {
|
||||
case "anthropic":
|
||||
case "bedrock":
|
||||
case "vertex":
|
||||
case "gemini":
|
||||
await this.updateGlobalState("previousModeModelId", apiConfiguration.apiModelId)
|
||||
break
|
||||
case "openrouter":
|
||||
await this.updateGlobalState("previousModeModelId", apiConfiguration.openRouterModelId)
|
||||
await this.updateGlobalState("previousModeModelInfo", apiConfiguration.openRouterModelInfo)
|
||||
break
|
||||
case "vscode-lm":
|
||||
await this.updateGlobalState("previousModeModelId", apiConfiguration.vsCodeLmModelSelector)
|
||||
break
|
||||
case "openai":
|
||||
await this.updateGlobalState("previousModeModelId", apiConfiguration.openAiModelId)
|
||||
await this.updateGlobalState("previousModeModelInfo", apiConfiguration.openAiModelInfo)
|
||||
break
|
||||
case "ollama":
|
||||
await this.updateGlobalState("previousModeModelId", apiConfiguration.ollamaModelId)
|
||||
break
|
||||
case "lmstudio":
|
||||
await this.updateGlobalState("previousModeModelId", apiConfiguration.lmStudioModelId)
|
||||
break
|
||||
case "litellm":
|
||||
await this.updateGlobalState("previousModeModelId", apiConfiguration.liteLlmModelId)
|
||||
break
|
||||
}
|
||||
|
||||
// Restore the model used in previous mode
|
||||
if (newApiProvider && newModelId) {
|
||||
await this.updateGlobalState("apiProvider", newApiProvider)
|
||||
switch (newApiProvider) {
|
||||
case "anthropic":
|
||||
case "bedrock":
|
||||
case "vertex":
|
||||
case "gemini":
|
||||
await this.updateGlobalState("apiModelId", newModelId)
|
||||
break
|
||||
case "openrouter":
|
||||
await this.updateGlobalState("openRouterModelId", newModelId)
|
||||
await this.updateGlobalState("openRouterModelInfo", newModelInfo)
|
||||
break
|
||||
case "vscode-lm":
|
||||
await this.updateGlobalState("vsCodeLmModelSelector", newModelId)
|
||||
break
|
||||
case "openai":
|
||||
await this.updateGlobalState("openAiModelId", newModelId)
|
||||
await this.updateGlobalState("openAiModelInfo", newModelInfo)
|
||||
break
|
||||
case "ollama":
|
||||
await this.updateGlobalState("ollamaModelId", newModelId)
|
||||
break
|
||||
case "lmstudio":
|
||||
await this.updateGlobalState("lmStudioModelId", newModelId)
|
||||
break
|
||||
case "litellm":
|
||||
await this.updateGlobalState("liteLlmModelId", newModelId)
|
||||
break
|
||||
}
|
||||
|
||||
if (this.cline) {
|
||||
const { apiConfiguration: updatedApiConfiguration } = await this.getState()
|
||||
this.cline.api = buildApiHandler(updatedApiConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
await this.updateGlobalState("chatSettings", message.chatSettings)
|
||||
await this.postStateToWebview()
|
||||
// console.log("chatSettings", message.chatSettings)
|
||||
if (this.cline) {
|
||||
this.cline.updateChatSettings(message.chatSettings)
|
||||
if (this.cline.isAwaitingPlanResponse && didSwitchToActMode) {
|
||||
this.cline.didRespondToPlanAskBySwitchingMode = true
|
||||
// this is necessary for the webview to update accordingly, but Cline instance will not send text back as feedback message
|
||||
await this.postMessageToWebview({
|
||||
type: "invoke",
|
||||
invoke: "sendMessage",
|
||||
text: message.chatContent?.message || "PLAN_MODE_TOGGLE_RESPONSE",
|
||||
images: message.chatContent?.images,
|
||||
})
|
||||
} else {
|
||||
this.cancelTask()
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
// case "relaunchChromeDebugMode":
|
||||
@@ -698,10 +773,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
await this.handleSignOut()
|
||||
break
|
||||
}
|
||||
case "showMcpView": {
|
||||
await this.postMessageToWebview({ type: "action", action: "mcpButtonClicked" })
|
||||
break
|
||||
}
|
||||
case "openMcpSettings": {
|
||||
const mcpSettingsFilePath = await this.mcpHub?.getMcpSettingsFilePath()
|
||||
if (mcpSettingsFilePath) {
|
||||
@@ -709,69 +780,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
}
|
||||
break
|
||||
}
|
||||
case "fetchMcpMarketplace": {
|
||||
await this.fetchMcpMarketplace(message.bool)
|
||||
break
|
||||
}
|
||||
case "downloadMcp": {
|
||||
if (message.mcpId) {
|
||||
// 1. Toggle to act mode if we are in plan mode
|
||||
const { chatSettings } = await this.getStateToPostToWebview()
|
||||
if (chatSettings.mode === "plan") {
|
||||
await this.togglePlanActModeWithChatSettings({ mode: "act" })
|
||||
}
|
||||
|
||||
// 2. Enable MCP settings if disabled
|
||||
// Enable MCP mode if disabled
|
||||
const mcpConfig = vscode.workspace.getConfiguration("cline.mcp")
|
||||
if (mcpConfig.get<string>("mode") !== "full") {
|
||||
await mcpConfig.update("mode", "full", true)
|
||||
}
|
||||
|
||||
// 3. download MCP
|
||||
await this.downloadMcp(message.mcpId)
|
||||
}
|
||||
break
|
||||
}
|
||||
case "silentlyRefreshMcpMarketplace": {
|
||||
await this.silentlyRefreshMcpMarketplace()
|
||||
break
|
||||
}
|
||||
// case "openMcpMarketplaceServerDetails": {
|
||||
// if (message.text) {
|
||||
// const response = await fetch(`https://api.cline.bot/v1/mcp/marketplace/item?mcpId=${message.mcpId}`)
|
||||
// const details: McpDownloadResponse = await response.json()
|
||||
|
||||
// if (details.readmeContent) {
|
||||
// // Disable markdown preview markers
|
||||
// const config = vscode.workspace.getConfiguration("markdown")
|
||||
// await config.update("preview.markEditorSelection", false, true)
|
||||
|
||||
// // Create URI with base64 encoded markdown content
|
||||
// const uri = vscode.Uri.parse(
|
||||
// `${DIFF_VIEW_URI_SCHEME}:${details.name} README?${Buffer.from(details.readmeContent).toString("base64")}`,
|
||||
// )
|
||||
|
||||
// // close existing
|
||||
// const tabs = vscode.window.tabGroups.all
|
||||
// .flatMap((tg) => tg.tabs)
|
||||
// .filter((tab) => tab.label && tab.label.includes("README") && tab.label.includes("Preview"))
|
||||
// for (const tab of tabs) {
|
||||
// await vscode.window.tabGroups.close(tab)
|
||||
// }
|
||||
|
||||
// // Show only the preview
|
||||
// await vscode.commands.executeCommand("markdown.showPreview", uri, {
|
||||
// sideBySide: true,
|
||||
// preserveFocus: true,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// this.postMessageToWebview({ type: "relinquishControl" })
|
||||
|
||||
// break
|
||||
// }
|
||||
case "toggleMcpServer": {
|
||||
try {
|
||||
await this.mcpHub?.toggleServerDisabled(message.serverName!, message.disabled!)
|
||||
@@ -796,16 +804,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
}
|
||||
break
|
||||
}
|
||||
case "deleteMcpServer": {
|
||||
if (message.serverName) {
|
||||
this.mcpHub?.deleteServer(message.serverName)
|
||||
}
|
||||
break
|
||||
}
|
||||
case "fetchLatestMcpServersFromHub": {
|
||||
this.mcpHub?.sendLatestMcpServers()
|
||||
break
|
||||
}
|
||||
case "searchCommits": {
|
||||
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0)
|
||||
if (cwd) {
|
||||
@@ -821,16 +819,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
}
|
||||
break
|
||||
}
|
||||
case "updateMcpTimeout": {
|
||||
try {
|
||||
if (message.serverName && message.timeout) {
|
||||
await this.mcpHub?.updateServerTimeout(message.serverName, message.timeout)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to update timeout for server ${message.serverName}:`, error)
|
||||
}
|
||||
break
|
||||
}
|
||||
case "openExtensionSettings": {
|
||||
const settingsFilter = message.text || ""
|
||||
await vscode.commands.executeCommand(
|
||||
@@ -839,6 +827,12 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
)
|
||||
break
|
||||
}
|
||||
case "toggleTelemetryOptin": {
|
||||
await vscode.workspace.getConfiguration().update("cline.enableTelemetry", true, true)
|
||||
await this.updateGlobalState("hideTelemetryOptIn", true)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
}
|
||||
// Add more switch case statements here as more webview message commands
|
||||
// are created within the webview context (i.e. inside media/main.js)
|
||||
}
|
||||
@@ -848,106 +842,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
)
|
||||
}
|
||||
|
||||
async togglePlanActModeWithChatSettings(chatSettings: ChatSettings, chatContent?: ChatContent) {
|
||||
const didSwitchToActMode = chatSettings.mode === "act"
|
||||
|
||||
// Get previous model info that we will revert to after saving current mode api info
|
||||
const {
|
||||
apiConfiguration,
|
||||
previousModeApiProvider: newApiProvider,
|
||||
previousModeModelId: newModelId,
|
||||
previousModeModelInfo: newModelInfo,
|
||||
} = await this.getState()
|
||||
|
||||
// Save the last model used in this mode
|
||||
await this.updateGlobalState("previousModeApiProvider", apiConfiguration.apiProvider)
|
||||
switch (apiConfiguration.apiProvider) {
|
||||
case "anthropic":
|
||||
case "bedrock":
|
||||
case "vertex":
|
||||
case "gemini":
|
||||
await this.updateGlobalState("previousModeModelId", apiConfiguration.apiModelId)
|
||||
break
|
||||
case "openrouter":
|
||||
await this.updateGlobalState("previousModeModelId", apiConfiguration.openRouterModelId)
|
||||
await this.updateGlobalState("previousModeModelInfo", apiConfiguration.openRouterModelInfo)
|
||||
break
|
||||
case "vscode-lm":
|
||||
await this.updateGlobalState("previousModeModelId", apiConfiguration.vsCodeLmModelSelector)
|
||||
break
|
||||
case "openai":
|
||||
await this.updateGlobalState("previousModeModelId", apiConfiguration.openAiModelId)
|
||||
await this.updateGlobalState("previousModeModelInfo", apiConfiguration.openAiModelInfo)
|
||||
break
|
||||
case "ollama":
|
||||
await this.updateGlobalState("previousModeModelId", apiConfiguration.ollamaModelId)
|
||||
break
|
||||
case "lmstudio":
|
||||
await this.updateGlobalState("previousModeModelId", apiConfiguration.lmStudioModelId)
|
||||
break
|
||||
case "litellm":
|
||||
await this.updateGlobalState("previousModeModelId", apiConfiguration.liteLlmModelId)
|
||||
break
|
||||
}
|
||||
|
||||
// Restore the model used in previous mode
|
||||
if (newApiProvider && newModelId) {
|
||||
await this.updateGlobalState("apiProvider", newApiProvider)
|
||||
switch (newApiProvider) {
|
||||
case "anthropic":
|
||||
case "bedrock":
|
||||
case "vertex":
|
||||
case "gemini":
|
||||
await this.updateGlobalState("apiModelId", newModelId)
|
||||
break
|
||||
case "openrouter":
|
||||
await this.updateGlobalState("openRouterModelId", newModelId)
|
||||
await this.updateGlobalState("openRouterModelInfo", newModelInfo)
|
||||
break
|
||||
case "vscode-lm":
|
||||
await this.updateGlobalState("vsCodeLmModelSelector", newModelId)
|
||||
break
|
||||
case "openai":
|
||||
await this.updateGlobalState("openAiModelId", newModelId)
|
||||
await this.updateGlobalState("openAiModelInfo", newModelInfo)
|
||||
break
|
||||
case "ollama":
|
||||
await this.updateGlobalState("ollamaModelId", newModelId)
|
||||
break
|
||||
case "lmstudio":
|
||||
await this.updateGlobalState("lmStudioModelId", newModelId)
|
||||
break
|
||||
case "litellm":
|
||||
await this.updateGlobalState("liteLlmModelId", newModelId)
|
||||
break
|
||||
}
|
||||
|
||||
if (this.cline) {
|
||||
const { apiConfiguration: updatedApiConfiguration } = await this.getState()
|
||||
this.cline.api = buildApiHandler(updatedApiConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
await this.updateGlobalState("chatSettings", chatSettings)
|
||||
await this.postStateToWebview()
|
||||
// console.log("chatSettings", message.chatSettings)
|
||||
if (this.cline) {
|
||||
this.cline.updateChatSettings(chatSettings)
|
||||
if (this.cline.isAwaitingPlanResponse && didSwitchToActMode) {
|
||||
this.cline.didRespondToPlanAskBySwitchingMode = true
|
||||
// this is necessary for the webview to update accordingly, but Cline instance will not send text back as feedback message
|
||||
await this.postMessageToWebview({
|
||||
type: "invoke",
|
||||
invoke: "sendMessage",
|
||||
text: chatContent?.message || "PLAN_MODE_TOGGLE_RESPONSE",
|
||||
images: chatContent?.images,
|
||||
})
|
||||
} else {
|
||||
this.cancelTask()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async subscribeEmail(email?: string) {
|
||||
if (!email) {
|
||||
return
|
||||
@@ -1130,176 +1024,6 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
}
|
||||
}
|
||||
|
||||
// MCP Marketplace
|
||||
|
||||
private async fetchMcpMarketplaceFromApi(silent: boolean = false): Promise<McpMarketplaceCatalog | undefined> {
|
||||
try {
|
||||
const response = await axios.get("https://api.cline.bot/v1/mcp/marketplace", {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.data) {
|
||||
throw new Error("Invalid response from MCP marketplace API")
|
||||
}
|
||||
|
||||
const catalog: McpMarketplaceCatalog = {
|
||||
items: (response.data || []).map((item: any) => ({
|
||||
...item,
|
||||
githubStars: item.githubStars ?? 0,
|
||||
downloadCount: item.downloadCount ?? 0,
|
||||
tags: item.tags ?? [],
|
||||
})),
|
||||
}
|
||||
|
||||
// Store in global state
|
||||
await this.updateGlobalState("mcpMarketplaceCatalog", catalog)
|
||||
return catalog
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch MCP marketplace:", error)
|
||||
if (!silent) {
|
||||
const errorMessage = error instanceof Error ? error.message : "Failed to fetch MCP marketplace"
|
||||
await this.postMessageToWebview({
|
||||
type: "mcpMarketplaceCatalog",
|
||||
error: errorMessage,
|
||||
})
|
||||
vscode.window.showErrorMessage(errorMessage)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
async silentlyRefreshMcpMarketplace() {
|
||||
try {
|
||||
const catalog = await this.fetchMcpMarketplaceFromApi(true)
|
||||
if (catalog) {
|
||||
await this.postMessageToWebview({
|
||||
type: "mcpMarketplaceCatalog",
|
||||
mcpMarketplaceCatalog: catalog,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to silently refresh MCP marketplace:", error)
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchMcpMarketplace(forceRefresh: boolean = false) {
|
||||
try {
|
||||
// Check if we have cached data
|
||||
const cachedCatalog = (await this.getGlobalState("mcpMarketplaceCatalog")) as McpMarketplaceCatalog | undefined
|
||||
if (!forceRefresh && cachedCatalog?.items) {
|
||||
await this.postMessageToWebview({
|
||||
type: "mcpMarketplaceCatalog",
|
||||
mcpMarketplaceCatalog: cachedCatalog,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const catalog = await this.fetchMcpMarketplaceFromApi(false)
|
||||
if (catalog) {
|
||||
await this.postMessageToWebview({
|
||||
type: "mcpMarketplaceCatalog",
|
||||
mcpMarketplaceCatalog: catalog,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to handle cached MCP marketplace:", error)
|
||||
const errorMessage = error instanceof Error ? error.message : "Failed to handle cached MCP marketplace"
|
||||
await this.postMessageToWebview({
|
||||
type: "mcpMarketplaceCatalog",
|
||||
error: errorMessage,
|
||||
})
|
||||
vscode.window.showErrorMessage(errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
private async downloadMcp(mcpId: string) {
|
||||
try {
|
||||
// First check if we already have this MCP server installed
|
||||
const servers = this.mcpHub?.getServers() || []
|
||||
const isInstalled = servers.some((server: McpServer) => server.name === mcpId)
|
||||
|
||||
if (isInstalled) {
|
||||
throw new Error("This MCP server is already installed")
|
||||
}
|
||||
|
||||
// Fetch server details from marketplace
|
||||
const response = await axios.post<McpDownloadResponse>(
|
||||
"https://api.cline.bot/v1/mcp/download",
|
||||
{ mcpId },
|
||||
{
|
||||
headers: { "Content-Type": "application/json" },
|
||||
timeout: 10000,
|
||||
},
|
||||
)
|
||||
|
||||
if (!response.data) {
|
||||
throw new Error("Invalid response from MCP marketplace API")
|
||||
}
|
||||
|
||||
console.log("[downloadMcp] Response from download API", { response })
|
||||
|
||||
const mcpDetails = response.data
|
||||
|
||||
// Validate required fields
|
||||
if (!mcpDetails.githubUrl) {
|
||||
throw new Error("Missing GitHub URL in MCP download response")
|
||||
}
|
||||
if (!mcpDetails.readmeContent) {
|
||||
throw new Error("Missing README content in MCP download response")
|
||||
}
|
||||
|
||||
// Send details to webview
|
||||
await this.postMessageToWebview({
|
||||
type: "mcpDownloadDetails",
|
||||
mcpDownloadDetails: mcpDetails,
|
||||
})
|
||||
|
||||
// Create task with context from README and added guidelines for MCP server installation
|
||||
const task = `Set up the MCP server from ${mcpDetails.githubUrl} while adhering to these MCP server installation rules:
|
||||
- Use "${mcpDetails.mcpId}" as the server name in cline_mcp_settings.json.
|
||||
- Use commands aligned with the user's shell and operating system best practices. The user's shell is: ${getShell()}.
|
||||
- Create the directory for the new MCP server before starting installation.
|
||||
- Follow the MCP servers README exactly—only deviate if it clearly conflicts with the user's OS, in which case proceed thoughtfully.
|
||||
- Ensure any steps requiring the use of pip, npm, or any other package manager, are followed as required.
|
||||
- After running each command, read its output carefully and adjust subsequent steps as needed based on that information.
|
||||
- Once installed, demonstrate the server's capabilities by using one of its tools.
|
||||
Here is the project's README to help you get started:\n\n${mcpDetails.readmeContent}\n${mcpDetails.llmsInstallationContent}`
|
||||
|
||||
// Initialize task and show chat view
|
||||
await this.initClineWithTask(task)
|
||||
await this.postMessageToWebview({
|
||||
type: "action",
|
||||
action: "chatButtonClicked",
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Failed to download MCP:", error)
|
||||
let errorMessage = "Failed to download MCP"
|
||||
|
||||
if (axios.isAxiosError(error)) {
|
||||
if (error.code === "ECONNABORTED") {
|
||||
errorMessage = "Request timed out. Please try again."
|
||||
} else if (error.response?.status === 404) {
|
||||
errorMessage = "MCP server not found in marketplace."
|
||||
} else if (error.response?.status === 500) {
|
||||
errorMessage = "Internal server error. Please try again later."
|
||||
} else if (!error.response && error.request) {
|
||||
errorMessage = "Network error. Please check your internet connection."
|
||||
}
|
||||
} else if (error instanceof Error) {
|
||||
errorMessage = error.message
|
||||
}
|
||||
|
||||
// Show error in both notification and marketplace UI
|
||||
vscode.window.showErrorMessage(errorMessage)
|
||||
await this.postMessageToWebview({
|
||||
type: "mcpDownloadDetails",
|
||||
error: errorMessage,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// OpenAi
|
||||
|
||||
async getOpenAiModels(baseUrl?: string, apiKey?: string) {
|
||||
@@ -1423,8 +1147,6 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
}
|
||||
|
||||
switch (rawModel.id) {
|
||||
case "anthropic/claude-3-7-sonnet":
|
||||
case "anthropic/claude-3-7-sonnet:beta":
|
||||
case "anthropic/claude-3.5-sonnet":
|
||||
case "anthropic/claude-3.5-sonnet:beta":
|
||||
// NOTE: this needs to be synced with api.ts/openrouter default model info
|
||||
@@ -1603,7 +1325,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
chatSettings,
|
||||
userInfo,
|
||||
authToken,
|
||||
mcpMarketplaceEnabled,
|
||||
hideTelemetryOptIn,
|
||||
} = await this.getState()
|
||||
|
||||
return {
|
||||
@@ -1621,8 +1343,10 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
browserSettings,
|
||||
chatSettings,
|
||||
isLoggedIn: !!authToken,
|
||||
advancedSettings: vscode.workspace.getConfiguration("cline"),
|
||||
vscMachineId: vscode.env.machineId,
|
||||
userInfo,
|
||||
mcpMarketplaceEnabled,
|
||||
hideTelemetryOptIn: hideTelemetryOptIn || false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1729,6 +1453,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
previousModeModelInfo,
|
||||
qwenApiLine,
|
||||
liteLlmApiKey,
|
||||
hideTelemetryOptIn,
|
||||
] = await Promise.all([
|
||||
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
|
||||
this.getGlobalState("apiModelId") as Promise<string | undefined>,
|
||||
@@ -1780,6 +1505,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
this.getGlobalState("previousModeModelInfo") as Promise<ModelInfo | undefined>,
|
||||
this.getGlobalState("qwenApiLine") as Promise<string | undefined>,
|
||||
this.getSecret("liteLlmApiKey") as Promise<string | undefined>,
|
||||
this.getGlobalState("hideTelemetryOptIn") as Promise<boolean | undefined>,
|
||||
])
|
||||
|
||||
let apiProvider: ApiProvider
|
||||
@@ -1800,8 +1526,6 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
.getConfiguration("cline.modelSettings.o3Mini")
|
||||
.get("reasoningEffort", "medium")
|
||||
|
||||
const mcpMarketplaceEnabled = vscode.workspace.getConfiguration("cline").get<boolean>("mcpMarketplace.enabled", true)
|
||||
|
||||
return {
|
||||
apiConfiguration: {
|
||||
apiProvider,
|
||||
@@ -1856,7 +1580,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
|
||||
previousModeApiProvider,
|
||||
previousModeModelId,
|
||||
previousModeModelInfo,
|
||||
mcpMarketplaceEnabled,
|
||||
hideTelemetryOptIn,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-1
@@ -7,6 +7,7 @@ import { Logger } from "./services/logging/Logger"
|
||||
import { createClineAPI } from "./exports"
|
||||
import "./utils/path" // necessary to have access to String.prototype.toPosix
|
||||
import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider"
|
||||
import posthog from "./services/analytics/PostHogClient"
|
||||
|
||||
/*
|
||||
Built using https://github.com/microsoft/vscode-webview-ui-toolkit
|
||||
@@ -57,6 +58,22 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
}),
|
||||
)
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidChangeConfiguration(async (e) => {
|
||||
if (e.affectsConfiguration("cline")) {
|
||||
Logger.log("Configuration changed")
|
||||
await sidebarProvider.postStateToWebview()
|
||||
const config = vscode.workspace.getConfiguration("cline")
|
||||
// we use optIn and optOut because we want to keep posthog active for feature flags
|
||||
if (config.get("enableTelemetry")) {
|
||||
posthog.optIn()
|
||||
} else {
|
||||
posthog.optOut()
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
const openClineInNewTab = async () => {
|
||||
Logger.log("Opening Cline in new tab")
|
||||
// (this example uses webviewProvider activation event which is necessary to deserialize cached webview, but since we use retainContextWhenHidden, we don't need to use that event)
|
||||
@@ -187,6 +204,7 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
|
||||
// This method is called when your extension is deactivated
|
||||
export function deactivate() {
|
||||
export async function deactivate() {
|
||||
Logger.log("Cline extension deactivated")
|
||||
await posthog.shutdown()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { PostHog } from "posthog-node"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
class PostHogClient {
|
||||
private static instance: PostHogClient
|
||||
private client: PostHog
|
||||
private distinctId: string = vscode.env.machineId
|
||||
|
||||
private constructor() {
|
||||
this.client = new PostHog("phc_5WnLHpYyC30Bsb7VSJ6DzcPXZ34JSF08DJLyM7svZ15", {
|
||||
host: "https://us.i.posthog.com",
|
||||
enableExceptionAutocapture: true,
|
||||
})
|
||||
}
|
||||
|
||||
public static getInstance(): PostHogClient {
|
||||
if (!PostHogClient.instance) {
|
||||
PostHogClient.instance = new PostHogClient()
|
||||
}
|
||||
return PostHogClient.instance
|
||||
}
|
||||
|
||||
public optIn(): void {
|
||||
this.client.identify({ distinctId: this.distinctId })
|
||||
this.client.optIn()
|
||||
}
|
||||
|
||||
public optOut(): void {
|
||||
this.client.optOut()
|
||||
}
|
||||
|
||||
public capture(event: { event: string; properties?: any }): void {
|
||||
this.client.capture({ distinctId: this.distinctId, event: event.event, properties: event.properties })
|
||||
}
|
||||
|
||||
public async shutdown(): Promise<void> {
|
||||
await this.client.shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
// Export a single instance
|
||||
export default PostHogClient.getInstance()
|
||||
+27
-90
@@ -16,7 +16,6 @@ import * as vscode from "vscode"
|
||||
import { z } from "zod"
|
||||
import { ClineProvider, GlobalFileNames } from "../../core/webview/ClineProvider"
|
||||
import {
|
||||
DEFAULT_MCP_TIMEOUT_SECONDS,
|
||||
McpMode,
|
||||
McpResource,
|
||||
McpResourceResponse,
|
||||
@@ -27,7 +26,7 @@ import {
|
||||
} from "../../shared/mcp"
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
import { arePathsEqual } from "../../utils/path"
|
||||
import { secondsToMs } from "../../utils/time"
|
||||
|
||||
export type McpConnection = {
|
||||
server: McpServer
|
||||
client: Client
|
||||
@@ -43,7 +42,6 @@ const StdioConfigSchema = z.object({
|
||||
env: z.record(z.string()).optional(),
|
||||
autoApprove: AutoApproveSchema.optional(),
|
||||
disabled: z.boolean().optional(),
|
||||
timeout: z.number().min(1).max(3600).optional().default(DEFAULT_MCP_TIMEOUT_SECONDS),
|
||||
})
|
||||
|
||||
const McpSettingsSchema = z.object({
|
||||
@@ -244,6 +242,28 @@ export class McpHub {
|
||||
}
|
||||
transport.start = async () => {} // No-op now, .connect() won't fail
|
||||
|
||||
// // Set up notification handlers
|
||||
// client.setNotificationHandler(
|
||||
// // @ts-ignore-next-line
|
||||
// { method: "notifications/tools/list_changed" },
|
||||
// async () => {
|
||||
// console.log(`Tools changed for server: ${name}`)
|
||||
// connection.server.tools = await this.fetchTools(name)
|
||||
// await this.notifyWebviewOfServerChanges()
|
||||
// },
|
||||
// )
|
||||
|
||||
// client.setNotificationHandler(
|
||||
// // @ts-ignore-next-line
|
||||
// { method: "notifications/resources/list_changed" },
|
||||
// async () => {
|
||||
// console.log(`Resources changed for server: ${name}`)
|
||||
// connection.server.resources = await this.fetchResources(name)
|
||||
// connection.server.resourceTemplates = await this.fetchResourceTemplates(name)
|
||||
// await this.notifyWebviewOfServerChanges()
|
||||
// },
|
||||
// )
|
||||
|
||||
// Connect
|
||||
await client.connect(transport)
|
||||
connection.server.status = "connected"
|
||||
@@ -323,6 +343,10 @@ export class McpHub {
|
||||
const connection = this.connections.find((conn) => conn.server.name === name)
|
||||
if (connection) {
|
||||
try {
|
||||
// connection.client.removeNotificationHandler("notifications/tools/list_changed")
|
||||
// connection.client.removeNotificationHandler("notifications/resources/list_changed")
|
||||
// connection.client.removeNotificationHandler("notifications/stderr")
|
||||
// connection.client.removeNotificationHandler("notifications/stderr")
|
||||
await connection.transport.close()
|
||||
await connection.client.close()
|
||||
} catch (error) {
|
||||
@@ -448,10 +472,6 @@ export class McpHub {
|
||||
})
|
||||
}
|
||||
|
||||
async sendLatestMcpServers() {
|
||||
await this.notifyWebviewOfServerChanges()
|
||||
}
|
||||
|
||||
// Using server
|
||||
|
||||
// Public methods for server management
|
||||
@@ -539,7 +559,6 @@ export class McpHub {
|
||||
if (connection.server.disabled) {
|
||||
throw new Error(`Server "${serverName}" is disabled`)
|
||||
}
|
||||
|
||||
return await connection.client.request(
|
||||
{
|
||||
method: "resources/read",
|
||||
@@ -563,17 +582,6 @@ export class McpHub {
|
||||
throw new Error(`Server "${serverName}" is disabled and cannot be used`)
|
||||
}
|
||||
|
||||
let timeout = secondsToMs(DEFAULT_MCP_TIMEOUT_SECONDS) // sdk expects ms
|
||||
|
||||
try {
|
||||
const config = JSON.parse(connection.server.config)
|
||||
const parsedConfig = StdioConfigSchema.parse(config)
|
||||
timeout = secondsToMs(parsedConfig.timeout)
|
||||
} catch (error) {
|
||||
console.error(`Failed to parse timeout configuration for server ${serverName}: ${error}`)
|
||||
// Continue with default timeout
|
||||
}
|
||||
|
||||
return await connection.client.request(
|
||||
{
|
||||
method: "tools/call",
|
||||
@@ -583,9 +591,6 @@ export class McpHub {
|
||||
},
|
||||
},
|
||||
CallToolResultSchema,
|
||||
{
|
||||
timeout,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -627,74 +632,6 @@ export class McpHub {
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteServer(serverName: string) {
|
||||
try {
|
||||
const settingsPath = await this.getMcpSettingsFilePath()
|
||||
const content = await fs.readFile(settingsPath, "utf-8")
|
||||
const config = JSON.parse(content)
|
||||
if (!config.mcpServers || typeof config.mcpServers !== "object") {
|
||||
config.mcpServers = {}
|
||||
}
|
||||
if (config.mcpServers[serverName]) {
|
||||
delete config.mcpServers[serverName]
|
||||
const updatedConfig = {
|
||||
mcpServers: config.mcpServers,
|
||||
}
|
||||
await fs.writeFile(settingsPath, JSON.stringify(updatedConfig, null, 2))
|
||||
await this.updateServerConnections(config.mcpServers)
|
||||
vscode.window.showInformationMessage(`Deleted ${serverName} MCP server`)
|
||||
} else {
|
||||
vscode.window.showWarningMessage(`${serverName} not found in MCP configuration`)
|
||||
}
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to delete MCP server: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
public async updateServerTimeout(serverName: string, timeout: number): Promise<void> {
|
||||
try {
|
||||
// Validate timeout against schema
|
||||
const setConfigResult = StdioConfigSchema.shape.timeout.safeParse(timeout)
|
||||
if (!setConfigResult.success) {
|
||||
throw new Error(`Invalid timeout value: ${timeout}. Must be between 1 and 3600 seconds.`)
|
||||
}
|
||||
|
||||
const settingsPath = await this.getMcpSettingsFilePath()
|
||||
const content = await fs.readFile(settingsPath, "utf-8")
|
||||
const config = JSON.parse(content)
|
||||
|
||||
if (!config.mcpServers?.[serverName]) {
|
||||
throw new Error(`Server "${serverName}" not found in settings`)
|
||||
}
|
||||
|
||||
// Update the timeout in the config
|
||||
config.mcpServers[serverName] = {
|
||||
...config.mcpServers[serverName],
|
||||
timeout,
|
||||
}
|
||||
|
||||
// Write updated config back to file
|
||||
await fs.writeFile(settingsPath, JSON.stringify(config, null, 2))
|
||||
|
||||
// Update server connections to apply the new timeout
|
||||
await this.updateServerConnections(config.mcpServers)
|
||||
|
||||
vscode.window.showInformationMessage(`Updated timeout to ${timeout} seconds`)
|
||||
} catch (error) {
|
||||
console.error("Failed to update server timeout:", error)
|
||||
if (error instanceof Error) {
|
||||
console.error("Error details:", error.message, error.stack)
|
||||
}
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to update server timeout: ${error instanceof Error ? error.message : String(error)}`,
|
||||
)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async dispose(): Promise<void> {
|
||||
this.removeAllFileWatchers()
|
||||
for (const connection of this.connections) {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
export interface ClineConfiguration {
|
||||
vsCodeLmModelSelector?: {
|
||||
vendor?: string
|
||||
family?: string
|
||||
}
|
||||
mcp: {
|
||||
mode: "full" | "server-use-only" | "off"
|
||||
}
|
||||
enableCheckpoints: boolean
|
||||
enableTelemetry: boolean
|
||||
}
|
||||
|
||||
export const DEFAULT_ADVANCED_SETTINGS: ClineConfiguration = {
|
||||
mcp: {
|
||||
mode: "full",
|
||||
},
|
||||
enableCheckpoints: true,
|
||||
enableTelemetry: false,
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
// type that represents json data that is sent from extension to webview, called ExtensionMessage and has 'type' enum which can be 'plusButtonClicked' or 'settingsButtonClicked' or 'hello'
|
||||
|
||||
import { GitCommit } from "../utils/git"
|
||||
import { ClineConfiguration } from "./AdvancedSettings"
|
||||
import { ApiConfiguration, ModelInfo } from "./api"
|
||||
import { AutoApprovalSettings } from "./AutoApprovalSettings"
|
||||
import { BrowserSettings } from "./BrowserSettings"
|
||||
import { ChatSettings } from "./ChatSettings"
|
||||
import { HistoryItem } from "./HistoryItem"
|
||||
import { McpServer, McpMarketplaceCatalog, McpMarketplaceItem, McpDownloadResponse } from "./mcp"
|
||||
import { McpServer } from "./mcp"
|
||||
import { WorkspaceConfiguration } from "vscode"
|
||||
|
||||
// webview will hold state
|
||||
export interface ExtensionMessage {
|
||||
@@ -27,8 +29,6 @@ export interface ExtensionMessage {
|
||||
| "vsCodeLmModels"
|
||||
| "requestVsCodeLmModels"
|
||||
| "emailSubscribed"
|
||||
| "mcpMarketplaceCatalog"
|
||||
| "mcpDownloadDetails"
|
||||
| "commitSearchResults"
|
||||
text?: string
|
||||
action?:
|
||||
@@ -50,9 +50,6 @@ export interface ExtensionMessage {
|
||||
openRouterModels?: Record<string, ModelInfo>
|
||||
openAiModels?: string[]
|
||||
mcpServers?: McpServer[]
|
||||
mcpMarketplaceCatalog?: McpMarketplaceCatalog
|
||||
error?: string
|
||||
mcpDownloadDetails?: McpDownloadResponse
|
||||
commits?: GitCommit[]
|
||||
}
|
||||
|
||||
@@ -75,12 +72,14 @@ export interface ExtensionState {
|
||||
chatSettings: ChatSettings
|
||||
isLoggedIn: boolean
|
||||
platform: Platform
|
||||
advancedSettings: WorkspaceConfiguration | ClineConfiguration
|
||||
vscMachineId: string
|
||||
hideTelemetryOptIn: boolean
|
||||
userInfo?: {
|
||||
displayName: string | null
|
||||
email: string | null
|
||||
photoURL: string | null
|
||||
}
|
||||
mcpMarketplaceEnabled?: boolean
|
||||
}
|
||||
|
||||
export interface ClineMessage {
|
||||
|
||||
@@ -29,10 +29,9 @@ export interface WebviewMessage {
|
||||
| "refreshOpenAiModels"
|
||||
| "openMcpSettings"
|
||||
| "restartMcpServer"
|
||||
| "deleteMcpServer"
|
||||
| "autoApprovalSettings"
|
||||
| "browserSettings"
|
||||
| "togglePlanActMode"
|
||||
| "chatSettings"
|
||||
| "checkpointDiff"
|
||||
| "checkpointRestore"
|
||||
| "taskCompletionViewChanges"
|
||||
@@ -44,13 +43,8 @@ export interface WebviewMessage {
|
||||
| "accountLoginClicked"
|
||||
| "accountLogoutClicked"
|
||||
| "subscribeEmail"
|
||||
| "fetchMcpMarketplace"
|
||||
| "downloadMcp"
|
||||
| "silentlyRefreshMcpMarketplace"
|
||||
| "searchCommits"
|
||||
| "showMcpView"
|
||||
| "fetchLatestMcpServersFromHub"
|
||||
| "updateMcpTimeout"
|
||||
| "toggleTelemetryOptin"
|
||||
// | "relaunchChromeDebugMode"
|
||||
text?: string
|
||||
disabled?: boolean
|
||||
@@ -63,8 +57,6 @@ export interface WebviewMessage {
|
||||
browserSettings?: BrowserSettings
|
||||
chatSettings?: ChatSettings
|
||||
chatContent?: ChatContent
|
||||
mcpId?: string
|
||||
timeout?: number // For updateMcpTimeout
|
||||
|
||||
// For toggleToolAutoApprove
|
||||
serverName?: string
|
||||
|
||||
+1
-32
@@ -80,19 +80,8 @@ export interface ModelInfo {
|
||||
// Anthropic
|
||||
// https://docs.anthropic.com/en/docs/about-claude/models // prices updated 2025-01-02
|
||||
export type AnthropicModelId = keyof typeof anthropicModels
|
||||
export const anthropicDefaultModelId: AnthropicModelId = "claude-3-7-sonnet-20250219"
|
||||
export const anthropicDefaultModelId: AnthropicModelId = "claude-3-5-sonnet-20241022"
|
||||
export const anthropicModels = {
|
||||
"claude-3-7-sonnet-20250219": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 200_000,
|
||||
supportsImages: true,
|
||||
supportsComputerUse: true,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 3.0,
|
||||
outputPrice: 15.0,
|
||||
cacheWritesPrice: 3.75,
|
||||
cacheReadsPrice: 0.3,
|
||||
},
|
||||
"claude-3-5-sonnet-20241022": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 200_000,
|
||||
@@ -141,17 +130,6 @@ export const anthropicModels = {
|
||||
export type BedrockModelId = keyof typeof bedrockModels
|
||||
export const bedrockDefaultModelId: BedrockModelId = "anthropic.claude-3-5-sonnet-20241022-v2:0"
|
||||
export const bedrockModels = {
|
||||
"anthropic.claude-3-7-sonnet-20250219-v1:0": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 200_000,
|
||||
supportsImages: true,
|
||||
supportsComputerUse: true,
|
||||
supportsPromptCache: true,
|
||||
inputPrice: 3.0,
|
||||
outputPrice: 15.0,
|
||||
cacheWritesPrice: 3.75,
|
||||
cacheReadsPrice: 0.3,
|
||||
},
|
||||
"anthropic.claude-3-5-sonnet-20241022-v2:0": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 200_000,
|
||||
@@ -225,15 +203,6 @@ export const openRouterDefaultModelInfo: ModelInfo = {
|
||||
export type VertexModelId = keyof typeof vertexModels
|
||||
export const vertexDefaultModelId: VertexModelId = "claude-3-5-sonnet-v2@20241022"
|
||||
export const vertexModels = {
|
||||
"claude-3-7-sonnet@20250219": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 200_000,
|
||||
supportsImages: true,
|
||||
supportsComputerUse: true,
|
||||
supportsPromptCache: false,
|
||||
inputPrice: 3.0,
|
||||
outputPrice: 15.0,
|
||||
},
|
||||
"claude-3-5-sonnet-v2@20241022": {
|
||||
maxTokens: 8192,
|
||||
contextWindow: 200_000,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
export const DEFAULT_MCP_TIMEOUT_SECONDS = 60 // matches Anthropic's default timeout in their MCP SDK
|
||||
|
||||
export type McpMode = "full" | "server-use-only" | "off"
|
||||
|
||||
export type McpServer = {
|
||||
@@ -11,7 +9,6 @@ export type McpServer = {
|
||||
resources?: McpResource[]
|
||||
resourceTemplates?: McpResourceTemplate[]
|
||||
disabled?: boolean
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
export type McpTool = {
|
||||
@@ -69,39 +66,3 @@ export type McpToolCallResponse = {
|
||||
>
|
||||
isError?: boolean
|
||||
}
|
||||
|
||||
export interface McpMarketplaceItem {
|
||||
mcpId: string
|
||||
githubUrl: string
|
||||
name: string
|
||||
author: string
|
||||
description: string
|
||||
codiconIcon: string
|
||||
logoUrl: string
|
||||
category: string
|
||||
tags: string[]
|
||||
requiresApiKey: boolean
|
||||
readmeContent?: string
|
||||
llmsInstallationContent?: string
|
||||
isRecommended: boolean
|
||||
githubStars: number
|
||||
downloadCount: number
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
lastGithubSync: string
|
||||
}
|
||||
|
||||
export interface McpMarketplaceCatalog {
|
||||
items: McpMarketplaceItem[]
|
||||
}
|
||||
|
||||
export interface McpDownloadResponse {
|
||||
mcpId: string
|
||||
githubUrl: string
|
||||
name: string
|
||||
author: string
|
||||
description: string
|
||||
readmeContent: string
|
||||
llmsInstallationContent: string
|
||||
requiresApiKey: boolean
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ describe("Chat Integration Tests", () => {
|
||||
break;
|
||||
case 'toggleMode':
|
||||
vscode.postMessage({
|
||||
type: 'togglePlanActMode',
|
||||
type: 'chatSettings',
|
||||
chatSettings: { mode: 'act' },
|
||||
chatContent: {
|
||||
message: "message test",
|
||||
@@ -84,7 +84,7 @@ describe("Chat Integration Tests", () => {
|
||||
// Set up state change listener
|
||||
const stateChangePromise = new Promise<any>((resolve) => {
|
||||
panel.webview.onDidReceiveMessage((message) => {
|
||||
if (message.type === "togglePlanActMode") {
|
||||
if (message.type === "chatSettings") {
|
||||
resolve(message)
|
||||
}
|
||||
})
|
||||
@@ -102,7 +102,7 @@ describe("Chat Integration Tests", () => {
|
||||
// Set up state change listener
|
||||
const stateChangePromise = new Promise<any>((resolve) => {
|
||||
panel.webview.onDidReceiveMessage((message) => {
|
||||
if (message.type === "togglePlanActMode") {
|
||||
if (message.type === "chatSettings") {
|
||||
resolve(message)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export function secondsToMs(seconds: number): number {
|
||||
return seconds * 1000
|
||||
}
|
||||
Generated
+59
-1124
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fuse.js": "^7.0.0",
|
||||
"fzf": "^0.5.2",
|
||||
"mermaid": "^11.4.1",
|
||||
"posthog-js": "^1.215.3",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -60,6 +60,7 @@
|
||||
"@types/node": "^20.x",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/vscode": "^1.84.0",
|
||||
"@types/vscode-webview": "^1.57.5",
|
||||
"jsdom": "^25.0.1",
|
||||
"vitest": "^2.1.9"
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
const rewire = require("rewire")
|
||||
const defaults = rewire("react-scripts/scripts/build.js")
|
||||
const config = defaults.__get__("config")
|
||||
const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin")
|
||||
const path = require("path")
|
||||
const fs = require("fs")
|
||||
|
||||
/* Modifying Webpack Configuration for 'shared' dir
|
||||
This section uses Rewire to modify Create React App's webpack configuration without ejecting. Rewire allows us to inject and alter the internal build scripts of CRA at runtime. This allows us to maintain a flexible project structure that keeps shared code outside the webview-ui/src directory, while still adhering to CRA's security model that typically restricts imports to within src/.
|
||||
@@ -21,9 +24,7 @@ Before, we would just import types from shared dir and specifying include (and a
|
||||
- Imports from the shared directory must use full paths relative to the src directory, without file extensions.
|
||||
- Example: import { someFunction } from '../../src/shared/utils/helpers'
|
||||
*/
|
||||
const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin")
|
||||
const path = require("path")
|
||||
const fs = require("fs")
|
||||
|
||||
// Get all files in the shared directory
|
||||
const sharedDir = path.resolve(__dirname, "..", "..", "src", "shared")
|
||||
|
||||
|
||||
+14
-1
@@ -9,14 +9,17 @@ import AccountView from "./components/account/AccountView"
|
||||
import { ExtensionStateContextProvider, useExtensionState } from "./context/ExtensionStateContext"
|
||||
import { vscode } from "./utils/vscode"
|
||||
import McpView from "./components/mcp/McpView"
|
||||
import posthog from "posthog-js"
|
||||
|
||||
const AppContent = () => {
|
||||
const { didHydrateState, showWelcome, shouldShowAnnouncement } = useExtensionState()
|
||||
const { didHydrateState, showWelcome, shouldShowAnnouncement, advancedSettings, vscMachineId, hideTelemetryOptIn } =
|
||||
useExtensionState()
|
||||
const [showSettings, setShowSettings] = useState(false)
|
||||
const [showHistory, setShowHistory] = useState(false)
|
||||
const [showMcp, setShowMcp] = useState(false)
|
||||
const [showAccount, setShowAccount] = useState(false)
|
||||
const [showAnnouncement, setShowAnnouncement] = useState(false)
|
||||
const telemetryEnabled = advancedSettings.enableTelemetry
|
||||
|
||||
const handleMessage = useCallback((e: MessageEvent) => {
|
||||
const message: ExtensionMessage = e.data
|
||||
@@ -60,6 +63,15 @@ const AppContent = () => {
|
||||
|
||||
useEvent("message", handleMessage)
|
||||
|
||||
useEffect(() => {
|
||||
if (telemetryEnabled) {
|
||||
posthog.identify(vscMachineId)
|
||||
posthog.opt_in_capturing()
|
||||
} else {
|
||||
posthog.opt_out_capturing()
|
||||
}
|
||||
}, [telemetryEnabled, vscMachineId])
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldShowAnnouncement) {
|
||||
setShowAnnouncement(true)
|
||||
@@ -93,6 +105,7 @@ const AppContent = () => {
|
||||
hideAnnouncement={() => {
|
||||
setShowAnnouncement(false)
|
||||
}}
|
||||
hideTelemetryOptIn={hideTelemetryOptIn}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { memo } from "react"
|
||||
import { getAsVar, VSC_DESCRIPTION_FOREGROUND, VSC_INACTIVE_SELECTION_BACKGROUND } from "../../utils/vscStyles"
|
||||
import { vscode } from "../../utils/vscode"
|
||||
|
||||
interface AnnouncementProps {
|
||||
version: string
|
||||
@@ -31,34 +30,29 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
|
||||
</h3>
|
||||
<ul style={{ margin: "0 0 8px", paddingLeft: "12px" }}>
|
||||
<li>
|
||||
<b>Introducing MCP Marketplace:</b> Discover and install the best MCP servers right from the extension, with
|
||||
new servers added regularly! Get started by going to the{" "}
|
||||
<span className="codicon codicon-extensions" style={{ marginRight: "4px", fontSize: 10 }}></span>
|
||||
<VSCodeLink
|
||||
onClick={() => {
|
||||
vscode.postMessage({ type: "showMcpView" })
|
||||
}}>
|
||||
MCP Servers tab
|
||||
<b>Plan/Act mode toggle:</b> Plan mode turns Cline into an architect that gathers information, asks clarifying
|
||||
questions, and designs a solution. Switch back to Act mode to let him execute the plan!{" "}
|
||||
<VSCodeLink href="https://x.com/sdrzn/status/1881761978986934582" style={{ display: "inline" }}>
|
||||
See a demo here.
|
||||
</VSCodeLink>
|
||||
.
|
||||
</li>
|
||||
<li>
|
||||
<b>Mermaid diagrams in Plan mode!</b> Cline can now visualize his plans using flowcharts, sequences,
|
||||
entity-relationships, and more. When he explains his approach using mermaid, you'll see a diagram right in
|
||||
chat that you can click to expand.
|
||||
<b>Quick API/model switching</b> with a new popup menu under the chat field
|
||||
</li>
|
||||
<li>
|
||||
Use <code>@terminal</code> to reference terminal contents, and <code>@git</code> to reference working changes
|
||||
and commits!
|
||||
<b>VS Code LM API</b> lets you use models from other extensions like GitHub Copilot
|
||||
</li>
|
||||
<li>
|
||||
New visual indicator for checkpoints after edits & commands, and automatic checkpoint at the start of each
|
||||
task.
|
||||
<b>MCP server improvements:</b> On/off toggle to disable servers when not in use, and Auto-approve option for
|
||||
individual tools
|
||||
</li>
|
||||
<li>
|
||||
In case you missed it, Cline now supports Checkpoints!{" "}
|
||||
<VSCodeLink href="https://x.com/sdrzn/status/1876378124126236949" style={{ display: "inline" }}>
|
||||
See it in action here.
|
||||
</VSCodeLink>
|
||||
</li>
|
||||
</ul>
|
||||
<VSCodeLink href="https://x.com/sdrzn/status/1892262424881090721" style={{ display: "inline" }}>
|
||||
See a demo of the changes here!
|
||||
</VSCodeLink>
|
||||
{/*<ul style={{ margin: "0 0 8px", paddingLeft: "12px" }}>
|
||||
<li>
|
||||
OpenRouter now supports prompt caching! They also have much higher rate limits than other providers,
|
||||
@@ -115,12 +109,9 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
|
||||
}}
|
||||
/>
|
||||
<p style={{ margin: "0" }}>
|
||||
Join us on{" "}
|
||||
<VSCodeLink style={{ display: "inline" }} href="https://x.com/cline">
|
||||
X,
|
||||
</VSCodeLink>{" "}
|
||||
Join our{" "}
|
||||
<VSCodeLink style={{ display: "inline" }} href="https://discord.gg/cline">
|
||||
discord,
|
||||
discord
|
||||
</VSCodeLink>{" "}
|
||||
or{" "}
|
||||
<VSCodeLink style={{ display: "inline" }} href="https://www.reddit.com/r/cline/">
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
} from "../../../../src/shared/ExtensionMessage"
|
||||
import { COMMAND_OUTPUT_STRING, COMMAND_REQ_APP_STRING } from "../../../../src/shared/combineCommandSequences"
|
||||
import { useExtensionState } from "../../context/ExtensionStateContext"
|
||||
import { findMatchingResourceOrTemplate, getMcpServerDisplayName } from "../../utils/mcp"
|
||||
import { findMatchingResourceOrTemplate } from "../../utils/mcp"
|
||||
import { vscode } from "../../utils/vscode"
|
||||
import { CheckpointControls, CheckpointOverlay } from "../common/CheckpointControls"
|
||||
import CodeAccordian, { cleanPathPrefix } from "../common/CodeAccordian"
|
||||
@@ -100,7 +100,7 @@ const ChatRow = memo(
|
||||
export default ChatRow
|
||||
|
||||
export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifiedMessage, isLast }: ChatRowContentProps) => {
|
||||
const { mcpServers, mcpMarketplaceCatalog } = useExtensionState()
|
||||
const { mcpServers } = useExtensionState()
|
||||
|
||||
const [seeNewChangesDisabled, setSeeNewChangesDisabled] = useState(false)
|
||||
|
||||
@@ -201,12 +201,9 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi
|
||||
marginBottom: "-1.5px",
|
||||
}}></span>
|
||||
),
|
||||
<span style={{ color: normalColor, fontWeight: "bold", wordBreak: "break-word" }}>
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>
|
||||
Cline wants to {mcpServerUse.type === "use_mcp_tool" ? "use a tool" : "access a resource"} on the{" "}
|
||||
<code style={{ wordBreak: "break-all" }}>
|
||||
{getMcpServerDisplayName(mcpServerUse.serverName, mcpMarketplaceCatalog)}
|
||||
</code>{" "}
|
||||
MCP server:
|
||||
<code>{mcpServerUse.serverName}</code> MCP server:
|
||||
</span>,
|
||||
]
|
||||
case "completion_result":
|
||||
|
||||
@@ -22,7 +22,6 @@ import Tooltip from "../common/Tooltip"
|
||||
import ApiOptions, { normalizeApiConfiguration } from "../settings/ApiOptions"
|
||||
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
|
||||
import ContextMenu from "./ContextMenu"
|
||||
import { ChatSettings } from "../../../../src/shared/ChatSettings"
|
||||
|
||||
interface ChatTextAreaProps {
|
||||
inputValue: string
|
||||
@@ -237,7 +236,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
const buttonRef = useRef<HTMLDivElement>(null)
|
||||
const [arrowPosition, setArrowPosition] = useState(0)
|
||||
const [menuPosition, setMenuPosition] = useState(0)
|
||||
const [shownTooltipMode, setShownTooltipMode] = useState<ChatSettings["mode"] | null>(null)
|
||||
|
||||
const [, metaKeyChar] = useMetaKeyDetection(platform)
|
||||
|
||||
@@ -655,7 +653,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
setTimeout(() => {
|
||||
const newMode = chatSettings.mode === "plan" ? "act" : "plan"
|
||||
vscode.postMessage({
|
||||
type: "togglePlanActMode",
|
||||
type: "chatSettings",
|
||||
chatSettings: {
|
||||
mode: newMode,
|
||||
},
|
||||
@@ -943,6 +941,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
/>
|
||||
<DynamicTextArea
|
||||
data-testid="chat-input"
|
||||
className="ph-no-capture"
|
||||
ref={(el) => {
|
||||
if (typeof ref === "function") {
|
||||
ref(el)
|
||||
@@ -1132,23 +1131,12 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
</ModelContainer>
|
||||
</ButtonGroup>
|
||||
<Tooltip
|
||||
visible={shownTooltipMode !== null}
|
||||
tipText={`In ${shownTooltipMode === "act" ? "Act" : "Plan"} mode, Cline will ${shownTooltipMode === "act" ? "complete the task immediately" : "gather information to architect a plan"}`}
|
||||
tipText={`In ${chatSettings.mode === "act" ? "Act" : "Plan"} mode, Cline will ${chatSettings.mode === "act" ? "complete the task immediately" : "gather information to architect a plan"}`}
|
||||
hintText={`Toggle w/ ${metaKeyChar}+Shift+A`}>
|
||||
<SwitchContainer data-testid="mode-switch" disabled={false} onClick={onModeToggle}>
|
||||
<Slider isAct={chatSettings.mode === "act"} isPlan={chatSettings.mode === "plan"} />
|
||||
<SwitchOption
|
||||
isActive={chatSettings.mode === "plan"}
|
||||
onMouseOver={() => setShownTooltipMode("plan")}
|
||||
onMouseLeave={() => setShownTooltipMode(null)}>
|
||||
Plan
|
||||
</SwitchOption>
|
||||
<SwitchOption
|
||||
isActive={chatSettings.mode === "act"}
|
||||
onMouseOver={() => setShownTooltipMode("act")}
|
||||
onMouseLeave={() => setShownTooltipMode(null)}>
|
||||
Act
|
||||
</SwitchOption>
|
||||
<SwitchOption isActive={chatSettings.mode === "plan"}>Plan</SwitchOption>
|
||||
<SwitchOption isActive={chatSettings.mode === "act"}>Act</SwitchOption>
|
||||
</SwitchContainer>
|
||||
</Tooltip>
|
||||
</ControlsContainer>
|
||||
|
||||
@@ -26,17 +26,19 @@ import BrowserSessionRow from "./BrowserSessionRow"
|
||||
import ChatRow from "./ChatRow"
|
||||
import ChatTextArea from "./ChatTextArea"
|
||||
import TaskHeader from "./TaskHeader"
|
||||
import TelemetryOptin from "./TelemetryOptin"
|
||||
|
||||
interface ChatViewProps {
|
||||
isHidden: boolean
|
||||
showAnnouncement: boolean
|
||||
hideAnnouncement: () => void
|
||||
showHistoryView: () => void
|
||||
hideTelemetryOptIn: boolean
|
||||
}
|
||||
|
||||
export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
|
||||
|
||||
const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryView }: ChatViewProps) => {
|
||||
const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryView, hideTelemetryOptIn }: ChatViewProps) => {
|
||||
const { version, clineMessages: messages, taskHistory, apiConfiguration } = useExtensionState()
|
||||
|
||||
//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined
|
||||
@@ -789,15 +791,21 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
||||
flexDirection: "column",
|
||||
paddingBottom: "10px",
|
||||
}}>
|
||||
{!hideTelemetryOptIn && <TelemetryOptin />}
|
||||
{showAnnouncement && <Announcement version={version} hideAnnouncement={hideAnnouncement} />}
|
||||
<div style={{ padding: "0 20px", flexShrink: 0 }}>
|
||||
<h2>What can I do for you?</h2>
|
||||
<p>
|
||||
Thanks to Claude 3.7 Sonnet's agentic coding capabilities, I can handle complex software development
|
||||
tasks step-by-step. With tools that let me create & edit files, explore complex projects, use the
|
||||
browser, and execute terminal commands (after you grant permission), I can assist you in ways that go
|
||||
beyond code completion or tech support. I can even use MCP to create new tools and extend my own
|
||||
capabilities.
|
||||
Thanks to{" "}
|
||||
<VSCodeLink
|
||||
href="https://www-cdn.anthropic.com/fed9cc193a14b84131812372d8d5857f8f304c52/Model_Card_Claude_3_Addendum.pdf"
|
||||
style={{ display: "inline" }}>
|
||||
Claude 3.5 Sonnet's agentic coding capabilities,
|
||||
</VSCodeLink>{" "}
|
||||
I can handle complex software development tasks step-by-step. With tools that let me create & edit
|
||||
files, explore complex projects, use the browser, and execute terminal commands (after you grant
|
||||
permission), I can assist you in ways that go beyond code completion or tech support. I can even use
|
||||
MCP to create new tools and extend my own capabilities.
|
||||
</p>
|
||||
</div>
|
||||
{taskHistory.length > 0 && <HistoryPreview showHistoryView={showHistoryView} />}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import { memo, useState, useEffect } from "react"
|
||||
import { getAsVar, VSC_INACTIVE_SELECTION_BACKGROUND } from "../../utils/vscStyles"
|
||||
import { vscode } from "../../utils/vscode"
|
||||
|
||||
const boxStyles = {
|
||||
backgroundColor: getAsVar(VSC_INACTIVE_SELECTION_BACKGROUND),
|
||||
borderRadius: "3px",
|
||||
padding: "12px 16px",
|
||||
margin: "5px 15px 5px 15px",
|
||||
position: "relative" as const,
|
||||
flexShrink: 0,
|
||||
minHeight: "120px",
|
||||
display: "flex",
|
||||
flexDirection: "column" as const,
|
||||
justifyContent: "center" as const,
|
||||
}
|
||||
|
||||
const TelemetryOptin = () => {
|
||||
const [showThankYou, setShowThankYou] = useState(false)
|
||||
const [isVisible, setIsVisible] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
if (showThankYou) {
|
||||
const fadeTimer = setTimeout(() => {
|
||||
setIsVisible(false)
|
||||
}, 1500)
|
||||
|
||||
const closeTimer = setTimeout(() => {
|
||||
vscode.postMessage({ type: "toggleTelemetryOptin", bool: true })
|
||||
}, 2000)
|
||||
|
||||
return () => {
|
||||
clearTimeout(fadeTimer)
|
||||
clearTimeout(closeTimer)
|
||||
}
|
||||
}
|
||||
}, [showThankYou])
|
||||
|
||||
const handleOptIn = () => {
|
||||
setShowThankYou(true)
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
vscode.postMessage({ type: "toggleTelemetryOptin", bool: false })
|
||||
}
|
||||
|
||||
if (showThankYou) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
...boxStyles,
|
||||
opacity: isVisible ? 1 : 0,
|
||||
transition: "opacity 0.5s ease-out",
|
||||
textAlign: "center",
|
||||
}}>
|
||||
<h3 style={{ margin: "0" }}>Thank you for helping improve Cline!</h3>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={boxStyles}>
|
||||
<h3 style={{ margin: "0 0 8px" }}>Help Improve Cline</h3>
|
||||
<p style={{ margin: "0 0 12px" }}>
|
||||
Would you like to help make Cline better by sending anonymous error reports and usage data? No personal or project
|
||||
information will be collected. You can change this setting anytime in{" "}
|
||||
<VSCodeLink onClick={() => vscode.postMessage({ type: "openExtensionSettings" })}>VS Code preferences</VSCodeLink>
|
||||
.{" "}
|
||||
<VSCodeLink href="https://github.com/cline/cline/blob/main/docs/PRIVACY.md" style={{ display: "inline" }}>
|
||||
Learn more
|
||||
</VSCodeLink>
|
||||
</p>
|
||||
<div style={{ display: "flex", gap: "8px" }}>
|
||||
<VSCodeButton appearance="primary" onClick={handleOptIn}>
|
||||
Opt In
|
||||
</VSCodeButton>
|
||||
<VSCodeButton appearance="secondary" onClick={handleCancel}>
|
||||
Cancel
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(TelemetryOptin)
|
||||
@@ -180,7 +180,7 @@ export const CheckmarkControl = ({ messageTs, isCheckpointCheckedOut }: Checkmar
|
||||
what will be reverted)
|
||||
</p>
|
||||
</RestoreOption>
|
||||
<RestoreOption>
|
||||
{/* <RestoreOption>
|
||||
<VSCodeButton
|
||||
onClick={handleRestoreTask}
|
||||
disabled={restoreTaskDisabled}
|
||||
@@ -192,7 +192,7 @@ export const CheckmarkControl = ({ messageTs, isCheckpointCheckedOut }: Checkmar
|
||||
Restore Task Only
|
||||
</VSCodeButton>
|
||||
<p>Deletes messages after this point (does not affect workspace files)</p>
|
||||
</RestoreOption>
|
||||
</RestoreOption> */}
|
||||
<RestoreOption>
|
||||
<VSCodeButton
|
||||
onClick={handleRestoreBoth}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import styled from "styled-components"
|
||||
|
||||
const StyledButton = styled(VSCodeButton)`
|
||||
--danger-button-bg: #c42b2b;
|
||||
--danger-button-hover: #a82424;
|
||||
--danger-button-active: #8f1f1f;
|
||||
|
||||
background-color: var(--danger-button-bg) !important;
|
||||
border-color: var(--danger-button-bg) !important;
|
||||
color: #ffffff !important;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--danger-button-hover) !important;
|
||||
border-color: var(--danger-button-hover) !important;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--danger-button-active) !important;
|
||||
border-color: var(--danger-button-active) !important;
|
||||
}
|
||||
`
|
||||
|
||||
interface DangerButtonProps extends React.ComponentProps<typeof VSCodeButton> {}
|
||||
|
||||
const DangerButton: React.FC<DangerButtonProps> = (props) => {
|
||||
return <StyledButton {...props} />
|
||||
}
|
||||
|
||||
export default DangerButton
|
||||
@@ -1,11 +1,10 @@
|
||||
import React, { memo, useEffect } from "react"
|
||||
import { memo, useEffect } from "react"
|
||||
import { useRemark } from "react-remark"
|
||||
import rehypeHighlight, { Options } from "rehype-highlight"
|
||||
import styled from "styled-components"
|
||||
import { visit } from "unist-util-visit"
|
||||
import { useExtensionState } from "../../context/ExtensionStateContext"
|
||||
import { CODE_BLOCK_BG_COLOR } from "./CodeBlock"
|
||||
import MermaidBlock from "./MermaidBlock"
|
||||
|
||||
interface MarkdownBlockProps {
|
||||
markdown?: string
|
||||
@@ -221,27 +220,7 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
|
||||
],
|
||||
rehypeReactOptions: {
|
||||
components: {
|
||||
pre: ({ node, children, ...preProps }: any) => {
|
||||
if (Array.isArray(children) && children.length === 1 && React.isValidElement(children[0])) {
|
||||
const child = children[0] as React.ReactElement<{ className?: string }>
|
||||
if (child.props?.className?.includes("language-mermaid")) {
|
||||
return child
|
||||
}
|
||||
}
|
||||
return (
|
||||
<StyledPre {...preProps} theme={theme}>
|
||||
{children}
|
||||
</StyledPre>
|
||||
)
|
||||
},
|
||||
code: (props: any) => {
|
||||
const className = props.className || ""
|
||||
if (className.includes("language-mermaid")) {
|
||||
const codeText = String(props.children || "")
|
||||
return <MermaidBlock code={codeText} />
|
||||
}
|
||||
return <code {...props} />
|
||||
},
|
||||
pre: ({ node, ...preProps }: any) => <StyledPre {...preProps} theme={theme} />,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import mermaid from "mermaid"
|
||||
import { useDebounceEffect } from "../../utils/useDebounceEffect"
|
||||
import styled from "styled-components"
|
||||
import { vscode } from "../../utils/vscode"
|
||||
|
||||
const MERMAID_THEME = {
|
||||
background: "#1e1e1e", // VS Code dark theme background
|
||||
textColor: "#ffffff", // Main text color
|
||||
mainBkg: "#2d2d2d", // Background for nodes
|
||||
nodeBorder: "#888888", // Border color for nodes
|
||||
lineColor: "#cccccc", // Lines connecting nodes
|
||||
primaryColor: "#3c3c3c", // Primary color for highlights
|
||||
primaryTextColor: "#ffffff", // Text in primary colored elements
|
||||
primaryBorderColor: "#888888",
|
||||
secondaryColor: "#2d2d2d", // Secondary color for alternate elements
|
||||
tertiaryColor: "#454545", // Third color for special elements
|
||||
|
||||
// Class diagram specific
|
||||
classText: "#ffffff",
|
||||
|
||||
// State diagram specific
|
||||
labelColor: "#ffffff",
|
||||
|
||||
// Sequence diagram specific
|
||||
actorLineColor: "#cccccc",
|
||||
actorBkg: "#2d2d2d",
|
||||
actorBorder: "#888888",
|
||||
actorTextColor: "#ffffff",
|
||||
|
||||
// Flow diagram specific
|
||||
fillType0: "#2d2d2d",
|
||||
fillType1: "#3c3c3c",
|
||||
fillType2: "#454545",
|
||||
}
|
||||
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
securityLevel: "loose",
|
||||
theme: "dark",
|
||||
themeVariables: {
|
||||
...MERMAID_THEME,
|
||||
fontSize: "16px",
|
||||
fontFamily: "var(--vscode-font-family, 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif)",
|
||||
|
||||
// Additional styling
|
||||
noteTextColor: "#ffffff",
|
||||
noteBkgColor: "#454545",
|
||||
noteBorderColor: "#888888",
|
||||
|
||||
// Improve contrast for special elements
|
||||
critBorderColor: "#ff9580",
|
||||
critBkgColor: "#803d36",
|
||||
|
||||
// Task diagram specific
|
||||
taskTextColor: "#ffffff",
|
||||
taskTextOutsideColor: "#ffffff",
|
||||
taskTextLightColor: "#ffffff",
|
||||
|
||||
// Numbers/sections
|
||||
sectionBkgColor: "#2d2d2d",
|
||||
sectionBkgColor2: "#3c3c3c",
|
||||
|
||||
// Alt sections in sequence diagrams
|
||||
altBackground: "#2d2d2d",
|
||||
|
||||
// Links
|
||||
linkColor: "#6cb6ff",
|
||||
|
||||
// Borders and lines
|
||||
compositeBackground: "#2d2d2d",
|
||||
compositeBorder: "#888888",
|
||||
titleColor: "#ffffff",
|
||||
},
|
||||
})
|
||||
|
||||
interface MermaidBlockProps {
|
||||
code: string
|
||||
}
|
||||
|
||||
export default function MermaidBlock({ code }: MermaidBlockProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
// 1) Whenever `code` changes, mark that we need to re-render a new chart
|
||||
useEffect(() => {
|
||||
setIsLoading(true)
|
||||
}, [code])
|
||||
|
||||
// 2) Debounce the actual parse/render
|
||||
useDebounceEffect(
|
||||
() => {
|
||||
if (containerRef.current) {
|
||||
containerRef.current.innerHTML = ""
|
||||
}
|
||||
mermaid
|
||||
.parse(code, { suppressErrors: true })
|
||||
.then((isValid) => {
|
||||
if (!isValid) {
|
||||
throw new Error("Invalid or incomplete Mermaid code")
|
||||
}
|
||||
const id = `mermaid-${Math.random().toString(36).substring(2)}`
|
||||
return mermaid.render(id, code)
|
||||
})
|
||||
.then(({ svg }) => {
|
||||
if (containerRef.current) {
|
||||
containerRef.current.innerHTML = svg
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.warn("Mermaid parse/render failed:", err)
|
||||
containerRef.current!.innerHTML = code.replace(/</g, "<").replace(/>/g, ">")
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false)
|
||||
})
|
||||
},
|
||||
500, // Delay 500ms
|
||||
[code], // Dependencies for scheduling
|
||||
)
|
||||
|
||||
/**
|
||||
* Called when user clicks the rendered diagram.
|
||||
* Converts the <svg> to a PNG and sends it to the extension.
|
||||
*/
|
||||
const handleClick = async () => {
|
||||
if (!containerRef.current) return
|
||||
const svgEl = containerRef.current.querySelector("svg")
|
||||
if (!svgEl) return
|
||||
|
||||
try {
|
||||
const pngDataUrl = await svgToPng(svgEl)
|
||||
vscode.postMessage({
|
||||
type: "openImage",
|
||||
text: pngDataUrl,
|
||||
})
|
||||
} catch (err) {
|
||||
console.error("Error converting SVG to PNG:", err)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<MermaidBlockContainer>
|
||||
{isLoading && <LoadingMessage>Generating mermaid diagram...</LoadingMessage>}
|
||||
|
||||
{/* The container for the final <svg> or raw code. */}
|
||||
<SvgContainer onClick={handleClick} ref={containerRef} $isLoading={isLoading} />
|
||||
</MermaidBlockContainer>
|
||||
)
|
||||
}
|
||||
|
||||
async function svgToPng(svgEl: SVGElement): Promise<string> {
|
||||
console.log("svgToPng function called")
|
||||
// Clone the SVG to avoid modifying the original
|
||||
const svgClone = svgEl.cloneNode(true) as SVGElement
|
||||
|
||||
// Get the original viewBox
|
||||
const viewBox = svgClone.getAttribute("viewBox")?.split(" ").map(Number) || []
|
||||
const originalWidth = viewBox[2] || svgClone.clientWidth
|
||||
const originalHeight = viewBox[3] || svgClone.clientHeight
|
||||
|
||||
// Calculate the scale factor to fit editor width while maintaining aspect ratio
|
||||
|
||||
// Unless we can find a way to get the actual editor window dimensions through the VS Code API (which might be possible but would require changes to the extension side),
|
||||
// the fixed width seems like a reliable approach.
|
||||
const editorWidth = 3_600
|
||||
|
||||
const scale = editorWidth / originalWidth
|
||||
const scaledHeight = originalHeight * scale
|
||||
|
||||
// Update SVG dimensions
|
||||
svgClone.setAttribute("width", `${editorWidth}`)
|
||||
svgClone.setAttribute("height", `${scaledHeight}`)
|
||||
|
||||
const serializer = new XMLSerializer()
|
||||
const svgString = serializer.serializeToString(svgClone)
|
||||
const svgDataUrl = "data:image/svg+xml;base64," + btoa(decodeURIComponent(encodeURIComponent(svgString)))
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement("canvas")
|
||||
canvas.width = editorWidth
|
||||
canvas.height = scaledHeight
|
||||
|
||||
const ctx = canvas.getContext("2d")
|
||||
if (!ctx) return reject("Canvas context not available")
|
||||
|
||||
// Fill background with Mermaid's dark theme background color
|
||||
ctx.fillStyle = MERMAID_THEME.background
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
ctx.imageSmoothingEnabled = true
|
||||
ctx.imageSmoothingQuality = "high"
|
||||
|
||||
ctx.drawImage(img, 0, 0, editorWidth, scaledHeight)
|
||||
resolve(canvas.toDataURL("image/png", 1.0))
|
||||
}
|
||||
img.onerror = reject
|
||||
img.src = svgDataUrl
|
||||
})
|
||||
}
|
||||
|
||||
const MermaidBlockContainer = styled.div`
|
||||
position: relative;
|
||||
margin: 8px 0;
|
||||
`
|
||||
|
||||
const LoadingMessage = styled.div`
|
||||
padding: 8px 0;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-style: italic;
|
||||
font-size: 0.9em;
|
||||
`
|
||||
|
||||
interface SvgContainerProps {
|
||||
$isLoading: boolean
|
||||
}
|
||||
|
||||
const SvgContainer = styled.div<SvgContainerProps>`
|
||||
opacity: ${(props) => (props.$isLoading ? 0.3 : 1)};
|
||||
min-height: 20px;
|
||||
transition: opacity 0.2s ease;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
} from "../../utils/vscStyles"
|
||||
|
||||
interface TooltipProps {
|
||||
visible: boolean
|
||||
hintText: string
|
||||
tipText: string
|
||||
children: React.ReactNode
|
||||
@@ -39,9 +38,14 @@ const Hint = styled.div`
|
||||
margin-top: 2px;
|
||||
`
|
||||
|
||||
const Tooltip: React.FC<TooltipProps> = ({ visible, tipText, hintText, children }) => {
|
||||
const Tooltip: React.FC<TooltipProps> = ({ tipText, hintText, children }) => {
|
||||
const [visible, setVisible] = useState(false)
|
||||
|
||||
const showTooltip = () => setVisible(true)
|
||||
const hideTooltip = () => setVisible(false)
|
||||
|
||||
return (
|
||||
<div style={{ position: "relative", display: "inline-block" }}>
|
||||
<div style={{ position: "relative", display: "inline-block" }} onMouseEnter={showTooltip} onMouseLeave={hideTooltip}>
|
||||
{children}
|
||||
{visible && (
|
||||
<TooltipBody>
|
||||
|
||||
@@ -1,48 +1,78 @@
|
||||
import {
|
||||
VSCodeButton,
|
||||
VSCodeLink,
|
||||
VSCodePanels,
|
||||
VSCodePanelTab,
|
||||
VSCodePanelView,
|
||||
VSCodeDropdown,
|
||||
VSCodeOption,
|
||||
} from "@vscode/webview-ui-toolkit/react"
|
||||
import { useEffect, useState } from "react"
|
||||
import styled from "styled-components"
|
||||
import { DEFAULT_MCP_TIMEOUT_SECONDS, McpServer } from "../../../../src/shared/mcp"
|
||||
import { useExtensionState } from "../../context/ExtensionStateContext"
|
||||
import { getMcpServerDisplayName } from "../../utils/mcp"
|
||||
import { VSCodeButton, VSCodeLink, VSCodePanels, VSCodePanelTab, VSCodePanelView } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useState } from "react"
|
||||
import { vscode } from "../../utils/vscode"
|
||||
import DangerButton from "../common/DangerButton"
|
||||
import McpMarketplaceView from "./marketplace/McpMarketplaceView"
|
||||
import McpResourceRow from "./McpResourceRow"
|
||||
import { useExtensionState } from "../../context/ExtensionStateContext"
|
||||
import { McpServer } from "../../../../src/shared/mcp"
|
||||
import McpToolRow from "./McpToolRow"
|
||||
import McpResourceRow from "./McpResourceRow"
|
||||
|
||||
type McpViewProps = {
|
||||
onDone: () => void
|
||||
}
|
||||
|
||||
const McpView = ({ onDone }: McpViewProps) => {
|
||||
const { mcpServers: servers, mcpMarketplaceEnabled } = useExtensionState()
|
||||
const [activeTab, setActiveTab] = useState(mcpMarketplaceEnabled ? "marketplace" : "installed")
|
||||
const { mcpServers: servers } = useExtensionState()
|
||||
|
||||
const handleTabChange = (tab: string) => {
|
||||
setActiveTab(tab)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!mcpMarketplaceEnabled && activeTab === "marketplace") {
|
||||
// If marketplace is disabled and we're on marketplace tab, switch to installed
|
||||
setActiveTab("installed")
|
||||
}
|
||||
}, [mcpMarketplaceEnabled, activeTab])
|
||||
|
||||
useEffect(() => {
|
||||
if (mcpMarketplaceEnabled) {
|
||||
vscode.postMessage({ type: "silentlyRefreshMcpMarketplace" })
|
||||
vscode.postMessage({ type: "fetchLatestMcpServersFromHub" })
|
||||
}
|
||||
}, [mcpMarketplaceEnabled])
|
||||
// const [servers, setServers] = useState<McpServer[]>([
|
||||
// // Add some mock servers for testing
|
||||
// {
|
||||
// name: "local-tools",
|
||||
// config: JSON.stringify({
|
||||
// mcpServers: {
|
||||
// "local-tools": {
|
||||
// command: "npx",
|
||||
// args: ["-y", "@modelcontextprotocol/server-tools"],
|
||||
// },
|
||||
// },
|
||||
// }),
|
||||
// status: "connected",
|
||||
// tools: [
|
||||
// {
|
||||
// name: "execute_command",
|
||||
// description: "Run a shell command on the local system",
|
||||
// },
|
||||
// {
|
||||
// name: "read_file",
|
||||
// description: "Read contents of a file from the filesystem",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// name: "postgres-db",
|
||||
// config: JSON.stringify({
|
||||
// mcpServers: {
|
||||
// "postgres-db": {
|
||||
// command: "npx",
|
||||
// args: ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"],
|
||||
// },
|
||||
// },
|
||||
// }),
|
||||
// status: "disconnected",
|
||||
// error: "Failed to connect to database: Connection refused",
|
||||
// },
|
||||
// {
|
||||
// name: "github-tools",
|
||||
// config: JSON.stringify({
|
||||
// mcpServers: {
|
||||
// "github-tools": {
|
||||
// command: "npx",
|
||||
// args: ["-y", "@modelcontextprotocol/server-github"],
|
||||
// },
|
||||
// },
|
||||
// }),
|
||||
// status: "connecting",
|
||||
// resources: [
|
||||
// {
|
||||
// uri: "github://repo/issues",
|
||||
// name: "Repository Issues",
|
||||
// },
|
||||
// {
|
||||
// uri: "github://repo/pulls",
|
||||
// name: "Pull Requests",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ])
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -60,146 +90,86 @@ const McpView = ({ onDone }: McpViewProps) => {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
padding: "10px 17px 5px 20px",
|
||||
padding: "10px 17px 10px 20px",
|
||||
}}>
|
||||
<h3 style={{ color: "var(--vscode-foreground)", margin: 0 }}>MCP Servers</h3>
|
||||
<VSCodeButton onClick={onDone}>Done</VSCodeButton>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1, overflow: "auto" }}>
|
||||
{/* Tabs container */}
|
||||
<div style={{ flex: 1, overflow: "auto", padding: "0 20px" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "1px",
|
||||
padding: "0 20px 0 20px",
|
||||
borderBottom: "1px solid var(--vscode-panel-border)",
|
||||
color: "var(--vscode-foreground)",
|
||||
fontSize: "13px",
|
||||
marginBottom: "16px",
|
||||
marginTop: "5px",
|
||||
}}>
|
||||
{mcpMarketplaceEnabled && (
|
||||
<TabButton isActive={activeTab === "marketplace"} onClick={() => handleTabChange("marketplace")}>
|
||||
Marketplace
|
||||
</TabButton>
|
||||
)}
|
||||
<TabButton isActive={activeTab === "installed"} onClick={() => handleTabChange("installed")}>
|
||||
Installed
|
||||
</TabButton>
|
||||
The{" "}
|
||||
<VSCodeLink href="https://github.com/modelcontextprotocol" style={{ display: "inline" }}>
|
||||
Model Context Protocol
|
||||
</VSCodeLink>{" "}
|
||||
enables communication with locally running MCP servers that provide additional tools and resources to extend
|
||||
Cline's capabilities. You can use{" "}
|
||||
<VSCodeLink href="https://github.com/modelcontextprotocol/servers" style={{ display: "inline" }}>
|
||||
community-made servers
|
||||
</VSCodeLink>{" "}
|
||||
or ask Cline to create new tools specific to your workflow (e.g., "add a tool that gets the latest npm docs").{" "}
|
||||
<VSCodeLink href="https://x.com/sdrzn/status/1867271665086074969" style={{ display: "inline" }}>
|
||||
See a demo here.
|
||||
</VSCodeLink>
|
||||
</div>
|
||||
|
||||
{/* Content container */}
|
||||
<div style={{ width: "100%" }}>
|
||||
{mcpMarketplaceEnabled && activeTab === "marketplace" && <McpMarketplaceView />}
|
||||
{activeTab === "installed" && (
|
||||
<div style={{ padding: "16px 20px" }}>
|
||||
<div
|
||||
style={{
|
||||
color: "var(--vscode-foreground)",
|
||||
fontSize: "13px",
|
||||
marginBottom: "16px",
|
||||
marginTop: "5px",
|
||||
}}>
|
||||
The{" "}
|
||||
<VSCodeLink href="https://github.com/modelcontextprotocol" style={{ display: "inline" }}>
|
||||
Model Context Protocol
|
||||
</VSCodeLink>{" "}
|
||||
enables communication with locally running MCP servers that provide additional tools and resources
|
||||
to extend Cline's capabilities. You can use{" "}
|
||||
<VSCodeLink href="https://github.com/modelcontextprotocol/servers" style={{ display: "inline" }}>
|
||||
community-made servers
|
||||
</VSCodeLink>{" "}
|
||||
or ask Cline to create new tools specific to your workflow (e.g., "add a tool that gets the latest
|
||||
npm docs").{" "}
|
||||
<VSCodeLink href="https://x.com/sdrzn/status/1867271665086074969" style={{ display: "inline" }}>
|
||||
See a demo here.
|
||||
</VSCodeLink>
|
||||
</div>
|
||||
{servers.length > 0 && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "10px",
|
||||
}}>
|
||||
{servers.map((server) => (
|
||||
<ServerRow key={server.name} server={server} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{servers.length > 0 ? (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "10px",
|
||||
}}>
|
||||
{servers.map((server) => (
|
||||
<ServerRow key={server.name} server={server} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: "12px",
|
||||
marginTop: 20,
|
||||
marginBottom: 20,
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
No MCP servers installed
|
||||
</div>
|
||||
)}
|
||||
{/* Server Configuration Button */}
|
||||
|
||||
{/* Settings Section */}
|
||||
<div style={{ marginBottom: "20px", marginTop: 10 }}>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
style={{ width: "100%", marginBottom: "5px" }}
|
||||
onClick={() => {
|
||||
vscode.postMessage({ type: "openMcpSettings" })
|
||||
}}>
|
||||
<span className="codicon codicon-server" style={{ marginRight: "6px" }}></span>
|
||||
Configure MCP Servers
|
||||
</VSCodeButton>
|
||||
|
||||
<div style={{ textAlign: "center" }}>
|
||||
<VSCodeLink
|
||||
onClick={() => {
|
||||
vscode.postMessage({
|
||||
type: "openExtensionSettings",
|
||||
text: "cline.mcp",
|
||||
})
|
||||
}}
|
||||
style={{ fontSize: "12px" }}>
|
||||
Advanced MCP Settings
|
||||
</VSCodeLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ marginTop: "10px", width: "100%" }}>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
style={{ width: "100%" }}
|
||||
onClick={() => {
|
||||
vscode.postMessage({ type: "openMcpSettings" })
|
||||
}}>
|
||||
<span className="codicon codicon-server" style={{ marginRight: "6px" }}></span>
|
||||
Configure MCP Servers
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
|
||||
{/* Advanced Settings Link */}
|
||||
<div style={{ textAlign: "center", marginTop: "5px" }}>
|
||||
<VSCodeLink
|
||||
onClick={() => {
|
||||
vscode.postMessage({
|
||||
type: "openExtensionSettings",
|
||||
text: "cline.mcp",
|
||||
})
|
||||
}}
|
||||
style={{ fontSize: "12px" }}>
|
||||
Advanced MCP Settings
|
||||
</VSCodeLink>
|
||||
</div>
|
||||
|
||||
{/* Bottom padding */}
|
||||
<div style={{ height: "20px" }} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledTabButton = styled.button<{ isActive: boolean }>`
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 2px solid ${(props) => (props.isActive ? "var(--vscode-foreground)" : "transparent")};
|
||||
color: ${(props) => (props.isActive ? "var(--vscode-foreground)" : "var(--vscode-descriptionForeground)")};
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
margin-bottom: -1px;
|
||||
font-family: inherit;
|
||||
|
||||
&:hover {
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
`
|
||||
|
||||
const TabButton = ({ children, isActive, onClick }: { children: React.ReactNode; isActive: boolean; onClick: () => void }) => (
|
||||
<StyledTabButton isActive={isActive} onClick={onClick}>
|
||||
{children}
|
||||
</StyledTabButton>
|
||||
)
|
||||
|
||||
// Server Row Component
|
||||
const ServerRow = ({ server }: { server: McpServer }) => {
|
||||
const { mcpMarketplaceCatalog } = useExtensionState()
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
const [isDeleting, setIsDeleting] = useState(false)
|
||||
|
||||
const getStatusColor = () => {
|
||||
switch (server.status) {
|
||||
@@ -218,36 +188,6 @@ const ServerRow = ({ server }: { server: McpServer }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const [timeout, setTimeout] = useState<string>(() => {
|
||||
try {
|
||||
const config = JSON.parse(server.config)
|
||||
return config.timeout?.toString() || DEFAULT_MCP_TIMEOUT_SECONDS.toString()
|
||||
} catch {
|
||||
return DEFAULT_MCP_TIMEOUT_SECONDS.toString()
|
||||
}
|
||||
})
|
||||
|
||||
const timeoutOptions = [
|
||||
{ value: "30", label: "30 seconds" },
|
||||
{ value: "60", label: "1 minute" },
|
||||
{ value: "300", label: "5 minutes" },
|
||||
{ value: "600", label: "10 minutes" },
|
||||
{ value: "1800", label: "30 minutes" },
|
||||
{ value: "3600", label: "1 hour" },
|
||||
]
|
||||
|
||||
const handleTimeoutChange = (e: any) => {
|
||||
const select = e.target as HTMLSelectElement
|
||||
const value = select.value
|
||||
const num = parseInt(value)
|
||||
setTimeout(value)
|
||||
vscode.postMessage({
|
||||
type: "updateMcpTimeout",
|
||||
serverName: server.name,
|
||||
timeout: num,
|
||||
})
|
||||
}
|
||||
|
||||
const handleRestart = () => {
|
||||
vscode.postMessage({
|
||||
type: "restartMcpServer",
|
||||
@@ -255,14 +195,6 @@ const ServerRow = ({ server }: { server: McpServer }) => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = () => {
|
||||
setIsDeleting(true)
|
||||
vscode.postMessage({
|
||||
type: "deleteMcpServer",
|
||||
serverName: server.name,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: "10px" }}>
|
||||
<div
|
||||
@@ -279,18 +211,7 @@ const ServerRow = ({ server }: { server: McpServer }) => {
|
||||
{!server.error && (
|
||||
<span className={`codicon codicon-chevron-${isExpanded ? "down" : "right"}`} style={{ marginRight: "8px" }} />
|
||||
)}
|
||||
<span
|
||||
style={{
|
||||
flex: 1,
|
||||
overflow: "hidden",
|
||||
wordBreak: "break-all",
|
||||
whiteSpace: "normal",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginRight: "4px",
|
||||
}}>
|
||||
{getMcpServerDisplayName(server.name, mcpMarketplaceCatalog)}
|
||||
</span>
|
||||
<span style={{ flex: 1 }}>{server.name}</span>
|
||||
<div style={{ display: "flex", alignItems: "center", marginRight: "8px" }} onClick={(e) => e.stopPropagation()}>
|
||||
<div
|
||||
role="switch"
|
||||
@@ -448,16 +369,6 @@ const ServerRow = ({ server }: { server: McpServer }) => {
|
||||
</VSCodePanelView>
|
||||
</VSCodePanels>
|
||||
|
||||
<div style={{ margin: "10px 7px" }}>
|
||||
<label style={{ display: "block", marginBottom: "4px", fontSize: "13px" }}>Request Timeout</label>
|
||||
<VSCodeDropdown style={{ width: "100%" }} value={timeout} onChange={handleTimeoutChange}>
|
||||
{timeoutOptions.map((option) => (
|
||||
<VSCodeOption key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</VSCodeOption>
|
||||
))}
|
||||
</VSCodeDropdown>
|
||||
</div>
|
||||
<VSCodeButton
|
||||
appearance="secondary"
|
||||
onClick={handleRestart}
|
||||
@@ -468,16 +379,6 @@ const ServerRow = ({ server }: { server: McpServer }) => {
|
||||
}}>
|
||||
{server.status === "connecting" ? "Restarting..." : "Restart Server"}
|
||||
</VSCodeButton>
|
||||
|
||||
<DangerButton
|
||||
onClick={handleDelete}
|
||||
disabled={isDeleting}
|
||||
style={{
|
||||
width: "calc(100% - 14px)",
|
||||
margin: "5px 7px 3px 7px",
|
||||
}}>
|
||||
{isDeleting ? "Deleting..." : "Delete Server"}
|
||||
</DangerButton>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -1,302 +0,0 @@
|
||||
import { useCallback, useState, useRef, useMemo } from "react"
|
||||
import styled from "styled-components"
|
||||
import { McpMarketplaceItem, McpServer } from "../../../../../src/shared/mcp"
|
||||
import { vscode } from "../../../utils/vscode"
|
||||
import { useEvent } from "react-use"
|
||||
|
||||
interface McpMarketplaceCardProps {
|
||||
item: McpMarketplaceItem
|
||||
installedServers: McpServer[]
|
||||
}
|
||||
|
||||
const McpMarketplaceCard = ({ item, installedServers }: McpMarketplaceCardProps) => {
|
||||
const isInstalled = installedServers.some((server) => server.name === item.mcpId)
|
||||
const [isDownloading, setIsDownloading] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const githubLinkRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const handleMessage = useCallback((event: MessageEvent) => {
|
||||
const message = event.data
|
||||
switch (message.type) {
|
||||
case "mcpDownloadDetails":
|
||||
setIsDownloading(false)
|
||||
break
|
||||
case "relinquishControl":
|
||||
setIsLoading(false)
|
||||
break
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEvent("message", handleMessage)
|
||||
|
||||
const githubAuthorUrl = useMemo(() => {
|
||||
const url = new URL(item.githubUrl)
|
||||
const pathParts = url.pathname.split("/")
|
||||
if (pathParts.length >= 2) {
|
||||
return `${url.origin}/${pathParts[1]}`
|
||||
}
|
||||
return item.githubUrl
|
||||
}, [item.githubUrl])
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>
|
||||
{`
|
||||
.mcp-card {
|
||||
cursor: pointer;
|
||||
outline: none !important;
|
||||
}
|
||||
.mcp-card:hover {
|
||||
background-color: var(--vscode-list-hoverBackground);
|
||||
}
|
||||
.mcp-card:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<a
|
||||
href={item.githubUrl}
|
||||
className="mcp-card"
|
||||
style={{
|
||||
padding: "14px 16px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 12,
|
||||
cursor: isLoading ? "wait" : "pointer",
|
||||
textDecoration: "none",
|
||||
color: "inherit",
|
||||
}}>
|
||||
{/* Main container with logo and content */}
|
||||
<div style={{ display: "flex", gap: "12px" }}>
|
||||
{/* Logo */}
|
||||
{item.logoUrl && (
|
||||
<img
|
||||
src={item.logoUrl}
|
||||
alt={`${item.name} logo`}
|
||||
style={{
|
||||
width: 42,
|
||||
height: 42,
|
||||
borderRadius: 4,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Content section */}
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
}}>
|
||||
{/* First row: name and install button */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
gap: "16px",
|
||||
}}>
|
||||
<h3
|
||||
style={{
|
||||
margin: 0,
|
||||
fontSize: "13px",
|
||||
fontWeight: 600,
|
||||
}}>
|
||||
{item.name}
|
||||
</h3>
|
||||
<div
|
||||
onClick={(e) => {
|
||||
e.preventDefault() // Prevent card click when clicking install
|
||||
e.stopPropagation() // Stop event from bubbling up to parent link
|
||||
if (!isInstalled && !isDownloading) {
|
||||
setIsDownloading(true)
|
||||
vscode.postMessage({
|
||||
type: "downloadMcp",
|
||||
mcpId: item.mcpId,
|
||||
})
|
||||
}
|
||||
}}
|
||||
style={{}}>
|
||||
<StyledInstallButton disabled={isInstalled || isDownloading} $isInstalled={isInstalled}>
|
||||
{isInstalled ? "Installed" : isDownloading ? "Installing..." : "Install"}
|
||||
</StyledInstallButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Second row: metadata */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
fontSize: "12px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
flexWrap: "wrap",
|
||||
minWidth: 0,
|
||||
rowGap: 0,
|
||||
}}>
|
||||
<a
|
||||
href={githubAuthorUrl}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
color: "var(--vscode-foreground)",
|
||||
minWidth: 0,
|
||||
opacity: 0.7,
|
||||
textDecoration: "none",
|
||||
border: "none !important",
|
||||
}}
|
||||
className="github-link"
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.opacity = "1"
|
||||
e.currentTarget.style.color = "var(--link-active-foreground)"
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.opacity = "0.7"
|
||||
e.currentTarget.style.color = "var(--vscode-foreground)"
|
||||
}}>
|
||||
<div style={{ display: "flex", gap: "4px", alignItems: "center" }} ref={githubLinkRef}>
|
||||
<span className="codicon codicon-github" style={{ fontSize: "14px" }} />
|
||||
<span
|
||||
style={{
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
wordBreak: "break-all",
|
||||
minWidth: 0,
|
||||
}}>
|
||||
{item.author}
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "4px",
|
||||
minWidth: 0,
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
<span className="codicon codicon-star-full" />
|
||||
<span style={{ wordBreak: "break-all" }}>{item.githubStars?.toLocaleString() ?? 0}</span>
|
||||
</div>
|
||||
{/* <div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "4px",
|
||||
minWidth: 0,
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
<span className="codicon codicon-cloud-download" />
|
||||
<span style={{ wordBreak: "break-all" }}>{item.downloadCount?.toLocaleString() ?? 0}</span>
|
||||
</div> */}
|
||||
{item.requiresApiKey && (
|
||||
<span className="codicon codicon-key" title="Requires API key" style={{ flexShrink: 0 }} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description and tags */}
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
|
||||
{/* {!item.isRecommended && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "4px",
|
||||
fontSize: "12px",
|
||||
color: "var(--vscode-notificationsWarningIcon-foreground)",
|
||||
marginTop: -3,
|
||||
marginBottom: -3,
|
||||
}}>
|
||||
<span className="codicon codicon-warning" style={{ fontSize: "14px" }} />
|
||||
<span>Community Made (use at your own risk)</span>
|
||||
</div>
|
||||
)} */}
|
||||
|
||||
<p style={{ fontSize: "13px", margin: 0 }}>{item.description}</p>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "6px",
|
||||
flexWrap: "nowrap",
|
||||
overflow: "hidden",
|
||||
position: "relative",
|
||||
}}>
|
||||
<span
|
||||
style={{
|
||||
fontSize: "10px",
|
||||
padding: "1px 4px",
|
||||
borderRadius: "3px",
|
||||
border: "1px solid color-mix(in srgb, var(--vscode-descriptionForeground) 50%, transparent)",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
whiteSpace: "nowrap",
|
||||
}}>
|
||||
{item.category}
|
||||
</span>
|
||||
{item.tags.map((tag, index) => (
|
||||
<span
|
||||
key={tag}
|
||||
style={{
|
||||
fontSize: "10px",
|
||||
padding: "1px 4px",
|
||||
borderRadius: "3px",
|
||||
border: "1px solid color-mix(in srgb, var(--vscode-descriptionForeground) 50%, transparent)",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
whiteSpace: "nowrap",
|
||||
display: "inline-flex",
|
||||
}}>
|
||||
{tag}
|
||||
{index === item.tags.length - 1 ? "" : ""}
|
||||
</span>
|
||||
))}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: "32px",
|
||||
background: "linear-gradient(to right, transparent, var(--vscode-sideBar-background))",
|
||||
pointerEvents: "none",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledInstallButton = styled.button<{ $isInstalled?: boolean }>`
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
padding: 2px 6px;
|
||||
border-radius: 2px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
background: ${(props) =>
|
||||
props.$isInstalled ? "var(--vscode-button-secondaryBackground)" : "var(--vscode-button-background)"};
|
||||
color: var(--vscode-button-foreground);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: ${(props) =>
|
||||
props.$isInstalled ? "var(--vscode-button-secondaryHoverBackground)" : "var(--vscode-button-hoverBackground)"};
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
background: ${(props) =>
|
||||
props.$isInstalled ? "var(--vscode-button-secondaryBackground)" : "var(--vscode-button-background)"};
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
`
|
||||
|
||||
export default McpMarketplaceCard
|
||||
@@ -1,286 +0,0 @@
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import {
|
||||
VSCodeButton,
|
||||
VSCodeProgressRing,
|
||||
VSCodeRadioGroup,
|
||||
VSCodeRadio,
|
||||
VSCodeDropdown,
|
||||
VSCodeOption,
|
||||
VSCodeTextField,
|
||||
} from "@vscode/webview-ui-toolkit/react"
|
||||
import { McpMarketplaceItem } from "../../../../../src/shared/mcp"
|
||||
import { useExtensionState } from "../../../context/ExtensionStateContext"
|
||||
import { vscode } from "../../../utils/vscode"
|
||||
import McpMarketplaceCard from "./McpMarketplaceCard"
|
||||
import McpSubmitCard from "./McpSubmitCard"
|
||||
const McpMarketplaceView = () => {
|
||||
const { mcpServers } = useExtensionState()
|
||||
const [items, setItems] = useState<McpMarketplaceItem[]>([])
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [isRefreshing, setIsRefreshing] = useState(false)
|
||||
const [searchQuery, setSearchQuery] = useState("")
|
||||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null)
|
||||
const [sortBy, setSortBy] = useState<"newest" | "stars" | "name">("newest")
|
||||
|
||||
const categories = useMemo(() => {
|
||||
const uniqueCategories = new Set(items.map((item) => item.category))
|
||||
return Array.from(uniqueCategories).sort()
|
||||
}, [items])
|
||||
|
||||
const filteredItems = useMemo(() => {
|
||||
return items
|
||||
.filter((item) => {
|
||||
const matchesSearch =
|
||||
searchQuery === "" ||
|
||||
item.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
item.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
item.tags.some((tag) => tag.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||
const matchesCategory = !selectedCategory || item.category === selectedCategory
|
||||
return matchesSearch && matchesCategory
|
||||
})
|
||||
.sort((a, b) => {
|
||||
switch (sortBy) {
|
||||
// case "downloadCount":
|
||||
// return b.downloadCount - a.downloadCount
|
||||
case "stars":
|
||||
return b.githubStars - a.githubStars
|
||||
case "name":
|
||||
return a.name.localeCompare(b.name)
|
||||
case "newest":
|
||||
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
})
|
||||
}, [items, searchQuery, selectedCategory, sortBy])
|
||||
|
||||
useEffect(() => {
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
const message = event.data
|
||||
if (message.type === "mcpMarketplaceCatalog") {
|
||||
if (message.error) {
|
||||
setError(message.error)
|
||||
} else {
|
||||
setItems(message.mcpMarketplaceCatalog?.items || [])
|
||||
setError(null)
|
||||
}
|
||||
setIsLoading(false)
|
||||
setIsRefreshing(false)
|
||||
} else if (message.type === "mcpDownloadDetails") {
|
||||
if (message.error) {
|
||||
setError(message.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", handleMessage)
|
||||
|
||||
// Fetch marketplace catalog
|
||||
fetchMarketplace()
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("message", handleMessage)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const fetchMarketplace = (forceRefresh: boolean = false) => {
|
||||
if (forceRefresh) {
|
||||
setIsRefreshing(true)
|
||||
} else {
|
||||
setIsLoading(true)
|
||||
}
|
||||
setError(null)
|
||||
vscode.postMessage({ type: "fetchMcpMarketplace", bool: forceRefresh })
|
||||
}
|
||||
|
||||
if (isLoading || isRefreshing) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
padding: "20px",
|
||||
}}>
|
||||
<VSCodeProgressRing />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
padding: "20px",
|
||||
gap: "12px",
|
||||
}}>
|
||||
<div style={{ color: "var(--vscode-errorForeground)" }}>{error}</div>
|
||||
<VSCodeButton appearance="secondary" onClick={() => fetchMarketplace(true)}>
|
||||
<span className="codicon codicon-refresh" style={{ marginRight: "6px" }} />
|
||||
Retry
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
}}>
|
||||
<div style={{ padding: "20px 20px 5px", display: "flex", flexDirection: "column", gap: "16px" }}>
|
||||
{/* Search row */}
|
||||
<VSCodeTextField
|
||||
style={{ width: "100%" }}
|
||||
placeholder="Search MCPs..."
|
||||
value={searchQuery}
|
||||
onInput={(e) => setSearchQuery((e.target as HTMLInputElement).value)}>
|
||||
<div
|
||||
slot="start"
|
||||
className="codicon codicon-search"
|
||||
style={{
|
||||
fontSize: 13,
|
||||
opacity: 0.8,
|
||||
}}
|
||||
/>
|
||||
{searchQuery && (
|
||||
<div
|
||||
className="codicon codicon-close"
|
||||
aria-label="Clear search"
|
||||
onClick={() => setSearchQuery("")}
|
||||
slot="end"
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</VSCodeTextField>
|
||||
|
||||
{/* Filter row */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
}}>
|
||||
<span
|
||||
style={{
|
||||
fontSize: "11px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
textTransform: "uppercase",
|
||||
fontWeight: 500,
|
||||
flexShrink: 0,
|
||||
}}>
|
||||
Filter:
|
||||
</span>
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
zIndex: 2,
|
||||
flex: 1,
|
||||
}}>
|
||||
<VSCodeDropdown
|
||||
style={{
|
||||
width: "100%",
|
||||
}}
|
||||
value={selectedCategory || ""}
|
||||
onChange={(e) => setSelectedCategory((e.target as HTMLSelectElement).value || null)}>
|
||||
<VSCodeOption value="">All Categories</VSCodeOption>
|
||||
{categories.map((category) => (
|
||||
<VSCodeOption key={category} value={category}>
|
||||
{category}
|
||||
</VSCodeOption>
|
||||
))}
|
||||
</VSCodeDropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sort row */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "8px",
|
||||
}}>
|
||||
<span
|
||||
style={{
|
||||
fontSize: "11px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
textTransform: "uppercase",
|
||||
fontWeight: 500,
|
||||
marginTop: "3px",
|
||||
}}>
|
||||
Sort:
|
||||
</span>
|
||||
<VSCodeRadioGroup
|
||||
style={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
marginTop: "-2.5px",
|
||||
}}
|
||||
value={sortBy}
|
||||
onChange={(e) => setSortBy((e.target as HTMLInputElement).value as typeof sortBy)}>
|
||||
{/* <VSCodeRadio value="downloadCount">Most Installs</VSCodeRadio> */}
|
||||
<VSCodeRadio value="newest">Newest</VSCodeRadio>
|
||||
<VSCodeRadio value="stars">GitHub Stars</VSCodeRadio>
|
||||
<VSCodeRadio value="name">Name</VSCodeRadio>
|
||||
</VSCodeRadioGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
{`
|
||||
.mcp-search-input,
|
||||
.mcp-select {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.mcp-search-input {
|
||||
min-width: 140px;
|
||||
}
|
||||
.mcp-search-input:focus,
|
||||
.mcp-select:focus {
|
||||
border-color: var(--vscode-focusBorder) !important;
|
||||
}
|
||||
.mcp-search-input:hover,
|
||||
.mcp-select:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
{filteredItems.length === 0 ? (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
padding: "20px",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
}}>
|
||||
{searchQuery || selectedCategory
|
||||
? "No matching MCP servers found"
|
||||
: "No MCP servers found in the marketplace"}
|
||||
</div>
|
||||
) : (
|
||||
filteredItems.map((item) => <McpMarketplaceCard key={item.mcpId} item={item} installedServers={mcpServers} />)
|
||||
)}
|
||||
<McpSubmitCard />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default McpMarketplaceView
|
||||
@@ -1,45 +0,0 @@
|
||||
const McpSubmitCard = () => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: "12px",
|
||||
padding: "15px",
|
||||
margin: "20px",
|
||||
backgroundColor: "var(--vscode-textBlockQuote-background)",
|
||||
borderRadius: "6px",
|
||||
}}>
|
||||
{/* Icon */}
|
||||
<i className="codicon codicon-add" style={{ fontSize: "18px" }} />
|
||||
|
||||
{/* Content */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: "4px",
|
||||
textAlign: "center",
|
||||
maxWidth: "480px",
|
||||
}}>
|
||||
<h3
|
||||
style={{
|
||||
margin: 0,
|
||||
fontSize: "14px",
|
||||
fontWeight: 600,
|
||||
color: "var(--vscode-foreground)",
|
||||
}}>
|
||||
Submit MCP Server
|
||||
</h3>
|
||||
<p style={{ fontSize: "13px", margin: 0, color: "var(--vscode-descriptionForeground)" }}>
|
||||
Help others discover great MCP servers by submitting an issue to{" "}
|
||||
<a href="https://github.com/cline/mcp-marketplace">github.com/cline/mcp-marketplace</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default McpSubmitCard
|
||||
@@ -201,6 +201,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
||||
{selectedProvider === "anthropic" && (
|
||||
<div>
|
||||
<VSCodeTextField
|
||||
className="ph-no-capture"
|
||||
value={apiConfiguration?.apiKey || ""}
|
||||
style={{ width: "100%" }}
|
||||
type="password"
|
||||
@@ -938,7 +939,7 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
|
||||
}}>
|
||||
The VS Code Language Model API allows you to run models provided by other VS Code extensions
|
||||
(including but not limited to GitHub Copilot). The easiest way to get started is to install the
|
||||
Copilot extension from the VS Marketplace and enabling Claude 3.7 Sonnet.
|
||||
Copilot extension from the VS Marketplace and enabling Claude 3.5 Sonnet.
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
@@ -228,8 +228,8 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup }
|
||||
If you're unsure which model to choose, Cline works best with{" "}
|
||||
<VSCodeLink
|
||||
style={{ display: "inline", fontSize: "inherit" }}
|
||||
onClick={() => handleModelChange("anthropic/claude-3-7-sonnet")}>
|
||||
anthropic/claude-3-7-sonnet.
|
||||
onClick={() => handleModelChange("anthropic/claude-3.5-sonnet")}>
|
||||
anthropic/claude-3.5-sonnet.
|
||||
</VSCodeLink>
|
||||
You can also try searching "free" for no-cost options currently available.
|
||||
</>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
import { useEvent } from "react-use"
|
||||
import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage"
|
||||
import { VSCodeButton, VSCodeLink, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
|
||||
import { useEffect, useState, useCallback } from "react"
|
||||
import { useExtensionState } from "../../context/ExtensionStateContext"
|
||||
import { validateApiConfiguration } from "../../utils/validate"
|
||||
import { vscode } from "../../utils/vscode"
|
||||
import ApiOptions from "../settings/ApiOptions"
|
||||
import { useEvent } from "react-use"
|
||||
import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage"
|
||||
|
||||
const WelcomeView = () => {
|
||||
const { apiConfiguration } = useExtensionState()
|
||||
@@ -58,13 +58,18 @@ const WelcomeView = () => {
|
||||
}}>
|
||||
<h2>Hi, I'm Cline</h2>
|
||||
<p>
|
||||
I can do all kinds of tasks thanks to breakthroughs in Claude 3.7 Sonnet's agentic coding capabilities and
|
||||
access to tools that let me create & edit files, explore complex projects, use the browser, and execute
|
||||
I can do all kinds of tasks thanks to the latest breakthroughs in{" "}
|
||||
<VSCodeLink
|
||||
href="https://www-cdn.anthropic.com/fed9cc193a14b84131812372d8d5857f8f304c52/Model_Card_Claude_3_Addendum.pdf"
|
||||
style={{ display: "inline" }}>
|
||||
Claude 3.5 Sonnet's agentic coding capabilities
|
||||
</VSCodeLink>{" "}
|
||||
and access to tools that let me create & edit files, explore complex projects, use the browser, and execute
|
||||
terminal commands (with your permission, of course). I can even use MCP to create new tools and extend my own
|
||||
capabilities.
|
||||
</p>
|
||||
|
||||
<b>To get started, this extension needs an API provider for Claude 3.7 Sonnet.</b>
|
||||
<b>To get started, this extension needs an API provider for Claude 3.5 Sonnet.</b>
|
||||
|
||||
<div
|
||||
style={{
|
||||
|
||||
@@ -4,11 +4,12 @@ import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "../../../src/shared/AutoApproval
|
||||
import { ExtensionMessage, ExtensionState, DEFAULT_PLATFORM } from "../../../src/shared/ExtensionMessage"
|
||||
import { ApiConfiguration, ModelInfo, openRouterDefaultModelId, openRouterDefaultModelInfo } from "../../../src/shared/api"
|
||||
import { findLastIndex } from "../../../src/shared/array"
|
||||
import { McpMarketplaceCatalog, McpServer } from "../../../src/shared/mcp"
|
||||
import { McpServer } from "../../../src/shared/mcp"
|
||||
import { convertTextMateToHljs } from "../utils/textMateToHljs"
|
||||
import { vscode } from "../utils/vscode"
|
||||
import { DEFAULT_BROWSER_SETTINGS } from "../../../src/shared/BrowserSettings"
|
||||
import { DEFAULT_CHAT_SETTINGS } from "../../../src/shared/ChatSettings"
|
||||
import { DEFAULT_ADVANCED_SETTINGS } from "../../../src/shared/AdvancedSettings"
|
||||
|
||||
interface ExtensionStateContextType extends ExtensionState {
|
||||
didHydrateState: boolean
|
||||
@@ -17,7 +18,6 @@ interface ExtensionStateContextType extends ExtensionState {
|
||||
openRouterModels: Record<string, ModelInfo>
|
||||
openAiModels: string[]
|
||||
mcpServers: McpServer[]
|
||||
mcpMarketplaceCatalog: McpMarketplaceCatalog
|
||||
filePaths: string[]
|
||||
setApiConfiguration: (config: ApiConfiguration) => void
|
||||
setCustomInstructions: (value?: string) => void
|
||||
@@ -39,6 +39,9 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
chatSettings: DEFAULT_CHAT_SETTINGS,
|
||||
isLoggedIn: false,
|
||||
platform: DEFAULT_PLATFORM,
|
||||
vscMachineId: "",
|
||||
advancedSettings: DEFAULT_ADVANCED_SETTINGS,
|
||||
hideTelemetryOptIn: false,
|
||||
})
|
||||
const [didHydrateState, setDidHydrateState] = useState(false)
|
||||
const [showWelcome, setShowWelcome] = useState(false)
|
||||
@@ -50,7 +53,7 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
|
||||
const [openAiModels, setOpenAiModels] = useState<string[]>([])
|
||||
const [mcpServers, setMcpServers] = useState<McpServer[]>([])
|
||||
const [mcpMarketplaceCatalog, setMcpMarketplaceCatalog] = useState<McpMarketplaceCatalog>({ items: [] })
|
||||
|
||||
const handleMessage = useCallback((event: MessageEvent) => {
|
||||
const message: ExtensionMessage = event.data
|
||||
switch (message.type) {
|
||||
@@ -122,12 +125,6 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
setMcpServers(message.mcpServers ?? [])
|
||||
break
|
||||
}
|
||||
case "mcpMarketplaceCatalog": {
|
||||
if (message.mcpMarketplaceCatalog) {
|
||||
setMcpMarketplaceCatalog(message.mcpMarketplaceCatalog)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
@@ -145,7 +142,6 @@ export const ExtensionStateContextProvider: React.FC<{
|
||||
openRouterModels,
|
||||
openAiModels,
|
||||
mcpServers,
|
||||
mcpMarketplaceCatalog,
|
||||
filePaths,
|
||||
setApiConfiguration: (value) =>
|
||||
setState((prevState) => ({
|
||||
|
||||
@@ -4,11 +4,16 @@ import "./index.css"
|
||||
import App from "./App"
|
||||
import reportWebVitals from "./reportWebVitals"
|
||||
import "../../node_modules/@vscode/codicons/dist/codicon.css"
|
||||
import { PostHogProvider } from "posthog-js/react"
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
<PostHogProvider
|
||||
apiKey="phc_5WnLHpYyC30Bsb7VSJ6DzcPXZ34JSF08DJLyM7svZ15"
|
||||
options={{ api_host: "https://us.i.posthog.com", capture_pageview: false }}>
|
||||
<App />
|
||||
</PostHogProvider>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { McpMarketplaceCatalog, McpResource, McpResourceTemplate } from "../../../src/shared/mcp"
|
||||
import { McpResource, McpResourceTemplate } from "../../../src/shared/mcp"
|
||||
|
||||
/**
|
||||
* Matches a URI against an array of URI templates and returns the matching template
|
||||
@@ -40,17 +40,3 @@ export function findMatchingResourceOrTemplate(
|
||||
// If no exact match, try to find a matching template
|
||||
return findMatchingTemplate(uri, templates)
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to convert an MCP server name to its display name using the marketplace catalog
|
||||
* @param serverName The server name/ID to look up
|
||||
* @param mcpMarketplaceCatalog The marketplace catalog containing server metadata
|
||||
* @returns The display name if found in catalog, otherwise returns the original server name
|
||||
*/
|
||||
export function getMcpServerDisplayName(serverName: string, mcpMarketplaceCatalog: McpMarketplaceCatalog): string {
|
||||
// Find matching item in marketplace catalog
|
||||
const catalogItem = mcpMarketplaceCatalog.items.find((item) => item.mcpId === serverName)
|
||||
|
||||
// Return display name if found, otherwise return original server name
|
||||
return catalogItem?.name || serverName
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import { useEffect, useRef } from "react"
|
||||
|
||||
type VoidFn = () => void
|
||||
|
||||
/**
|
||||
* Runs `effectRef.current()` after `delay` ms whenever any of the `deps` change,
|
||||
* but cancels/re-schedules if they change again before the delay.
|
||||
*/
|
||||
export function useDebounceEffect(effect: VoidFn, delay: number, deps: any[]) {
|
||||
const callbackRef = useRef<VoidFn>(effect)
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
|
||||
|
||||
// Keep callbackRef current
|
||||
useEffect(() => {
|
||||
callbackRef.current = effect
|
||||
}, [effect])
|
||||
|
||||
useEffect(() => {
|
||||
// Clear any queued call
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
}
|
||||
|
||||
// Schedule a new call
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
// always call the *latest* version of effect
|
||||
callbackRef.current()
|
||||
}, delay)
|
||||
|
||||
// Cleanup on unmount or next effect
|
||||
return () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current)
|
||||
}
|
||||
}
|
||||
|
||||
// We want to re‐schedule if any item in `deps` changed,
|
||||
// or if `delay` changed.
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [delay, ...deps])
|
||||
}
|
||||
Reference in New Issue
Block a user