Merge branch 'main' of https://github.com/RooVetGit/Roo-Cline into feature/pupeteer-updates

This commit is contained in:
a8trejo 2024-12-11 14:30:14 -08:00
commit 8e9218cfbb
27 changed files with 1650 additions and 119 deletions

View file

@ -1,5 +1,5 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
"$schema": "https://unpkg.com/@changesets/config@3.0.4/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],

View file

@ -1,11 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Feature Request
url: https://github.com/cline/cline/discussions/categories/feature-requests?discussions_q=is%3Aopen+category%3A%22Feature+Requests%22+sort%3Atop
about: Share and vote on feature requests for Cline
- name: Cline Discord
url: https://discord.gg/cline
about: Join our Discord community for discussions and support
- name: Other Questions?
url: https://x.com/sdrzn
about: Contact the developer on X @sdrzn for other inquiries

View file

@ -0,0 +1,86 @@
name: AI Release Notes
description: Generate AI release notes using git and openai, outputs 'RELEASE_NOTES' and 'OPENAI_PROMPT'
inputs:
OPENAI_API_KEY:
required: true
type: string
GHA_PAT:
required: true
type: string
model_name:
required: false
type: string
default: gpt-4o-mini
repo_path:
required: false
type: string
custom_prompt:
required: false
default: ''
type: string
git_ref:
required: false
type: string
default: ''
head_ref:
required: false
type: string
default: main
base_ref:
required: false
type: string
default: main
outputs:
RELEASE_NOTES:
description: "AI generated release notes"
value: ${{ steps.ai_release_notes.outputs.RELEASE_NOTES }}
OPENAI_PROMPT:
description: "Prompt used to generate release notes"
value: ${{ steps.ai_prompt.outputs.OPENAI_PROMPT }}
env:
GITHUB_REF: ${{ inputs.git_ref == '' && github.event.pull_request.head.ref || inputs.git_ref }}
BASE_REF: ${{ inputs.base_ref == '' && github.base_ref || inputs.base_ref }}
HEAD_REF: ${{ inputs.head_ref == '' && github.event.pull_request.head.sha || inputs.head_ref }}
runs:
using: "composite"
steps:
- uses: actions/checkout@v4
with:
repository: ${{ inputs.repo_path }}
token: ${{ inputs.GHA_PAT }}
ref: ${{ env.GITHUB_REF }}
fetch-depth: 0
- name: Set Workspace
shell: bash
run: |
pip install tiktoken
pip install pytz
# Github outputs: 'OPENAI_PROMPT'
- name: Add Git Info to base prompt
id: ai_prompt
shell: bash
env:
BASE_REF: ${{ env.BASE_REF }}
HEAD_SHA: ${{ env.HEAD_SHA }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_BODY: ${{ github.event.pull_request.body }}
MODEL_NAME: ${{ inputs.model_name }}
CUSTOM_PROMPT: ${{ inputs.custom_prompt }} # Default: ''
run: python .github/scripts/release-notes-prompt.py
# Github outputs: 'RELEASE_NOTES'
- name: Generate AI release notes
id: ai_release_notes
shell: bash
env:
OPENAI_API_KEY: ${{ inputs.OPENAI_API_KEY }}
CUSTOM_PROMPT: ${{ steps.ai_prompt.outputs.OPENAI_PROMPT }}
MODEL_NAME: ${{ inputs.model_name }}
run: python .github/scripts/ai-release-notes.py

11
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

28
.github/pull_request_template.md vendored Normal file
View file

@ -0,0 +1,28 @@
<!-- **Note:** Consider creating PRs as a DRAFT. For early feedback and self-review. -->
## Description
## Type of change
<!-- Please ignore options that are not relevant -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
## How Has This Been Tested?
<!-- Please describe the tests that you ran to verify your changes -->
## Checklist:
<!-- Go over all the following points, and put an `x` in all the boxes that apply -->
- [ ] My code follows the patterns of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
## Additional context
<!-- Add any other context or screenshots about the pull request here -->
## Related Issues
<!-- List any related issues here. Use the GitHub issue linking syntax: #issue-number -->
## Reviewers
<!-- @mention specific team members or individuals who should review this PR -->

123
.github/scripts/ai-release-notes.py vendored Normal file
View file

@ -0,0 +1,123 @@
"""
AI-powered release notes generator that creates concise and informative release notes from git changes.
This script uses OpenAI's API to analyze git changes (summary, diff, and commit log) and generate
well-formatted release notes in markdown. It focuses on important changes and their impact,
particularly highlighting new types and schemas while avoiding repetitive information.
Environment Variables Required:
OPENAI_API_KEY: OpenAI API key for authentication
CHANGE_SUMMARY: Summary of changes made (optional if CUSTOM_PROMPT provided)
CHANGE_DIFF: Git diff of changes (optional if CUSTOM_PROMPT provided)
CHANGE_LOG: Git commit log (optional if CUSTOM_PROMPT provided)
GITHUB_OUTPUT: Path to GitHub output file
CUSTOM_PROMPT: Custom prompt to override default (optional)
"""
import os
import requests # type: ignore
import json
import tiktoken # type: ignore
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
CHANGE_SUMMARY = os.environ.get('CHANGE_SUMMARY', '')
CHANGE_DIFF = os.environ.get('CHANGE_DIFF', '')
CHANGE_LOG = os.environ.get('CHANGE_LOG', '')
GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT")
OPEN_AI_BASE_URL = "https://api.openai.com/v1"
OPEN_API_HEADERS = {"Authorization": f"Bearer {OPENAI_API_KEY}", "Content-Type": "application/json"}
CUSTOM_PROMPT = os.environ.get('CUSTOM_PROMPT', '')
MODEL_NAME = os.environ.get('MODEL_NAME', 'gpt-3.5-turbo-16k')
def num_tokens_from_string(string: str, model_name: str) -> int:
"""
Calculate the number of tokens in a text string for a specific model.
Args:
string: The input text to count tokens for
model_name: Name of the OpenAI model to use for token counting
Returns:
int: Number of tokens in the input string
"""
encoding = tiktoken.encoding_for_model(model_name)
num_tokens = len(encoding.encode(string))
return num_tokens
def truncate_to_token_limit(text, max_tokens, model_name):
"""
Truncate text to fit within a maximum token limit for a specific model.
Args:
text: The input text to truncate
max_tokens: Maximum number of tokens allowed
model_name: Name of the OpenAI model to use for tokenization
Returns:
str: Truncated text that fits within the token limit
"""
encoding = tiktoken.encoding_for_model(model_name)
encoded = encoding.encode(text)
truncated = encoded[:max_tokens]
return encoding.decode(truncated)
def generate_release_notes(model_name):
"""
Generate release notes using OpenAI's API based on git changes.
Uses the GPT-3.5-turbo model to analyze change summary, commit log, and code diff
to generate concise and informative release notes in markdown format. The notes
focus on important changes and their impact, with sections for new types/schemas
and other updates.
Returns:
str: Generated release notes in markdown format
Raises:
requests.exceptions.RequestException: If the OpenAI API request fails
"""
max_tokens = 14000 # Reserve some tokens for the response
# Truncate inputs if necessary to fit within token limits
change_summary = '' if CUSTOM_PROMPT else truncate_to_token_limit(CHANGE_SUMMARY, 1000, model_name)
change_log = '' if CUSTOM_PROMPT else truncate_to_token_limit(CHANGE_LOG, 2000, model_name)
change_diff = '' if CUSTOM_PROMPT else truncate_to_token_limit(CHANGE_DIFF, max_tokens - num_tokens_from_string(change_summary, model_name) - num_tokens_from_string(change_log, model_name) - 1000, model_name)
url = f"{OPEN_AI_BASE_URL}/chat/completions"
# Construct prompt for OpenAI API
openai_prompt = CUSTOM_PROMPT if CUSTOM_PROMPT else f"""Based on the following summary of changes, commit log and code diff, please generate concise and informative release notes:
Summary of changes:
{change_summary}
Commit log:
{change_log}
Code Diff:
{json.dumps(change_diff)}
"""
data = {
"model": model_name,
"messages": [{"role": "user", "content": openai_prompt}],
"temperature": 0.7,
"max_tokens": 1000,
}
print("----------------------------------------------------------------------------------------------------------")
print("POST request to OpenAI")
print("----------------------------------------------------------------------------------------------------------")
ai_response = requests.post(url, headers=OPEN_API_HEADERS, json=data)
print(f"Status Code: {str(ai_response.status_code)}")
print(f"Response: {ai_response.text}")
ai_response.raise_for_status()
return ai_response.json()["choices"][0]["message"]["content"]
release_notes = generate_release_notes(MODEL_NAME)
print("----------------------------------------------------------------------------------------------------------")
print("OpenAI generated release notes")
print("----------------------------------------------------------------------------------------------------------")
print(release_notes)
# Write the release notes to GITHUB_OUTPUT
with open(GITHUB_OUTPUT, "a") as outputs_file:
outputs_file.write(f"RELEASE_NOTES<<EOF\n{release_notes}\nEOF")

View file

@ -0,0 +1,52 @@
import os
import re
import subprocess
def run_git_command(command):
result = subprocess.getoutput(command)
print(f"Git Command: {command}")
print(f"Git Output: {result}")
return result
def parse_merge_commit(line):
# Parse merge commit messages like:
# "355dc82 Merge pull request #71 from RooVetGit/better-error-handling"
pattern = r"([a-f0-9]+)\s+Merge pull request #(\d+) from (.+)"
match = re.match(pattern, line)
if match:
sha, pr_number, branch = match.groups()
return {
'sha': sha,
'pr_number': pr_number,
'branch': branch
}
return None
def get_version_refs():
# Get the merge commits with full message
command = 'git log --merges --pretty=oneline -n 3'
result = run_git_command(command)
if result:
commits = result.split('\n')
if len(commits) >= 3:
# Parse HEAD~1 (PR to generate notes for)
head_info = parse_merge_commit(commits[1])
# Parse HEAD~2 (previous PR to compare against)
base_info = parse_merge_commit(commits[2])
if head_info and base_info:
# Set output for GitHub Actions
with open(os.environ['GITHUB_OUTPUT'], 'a') as gha_outputs:
gha_outputs.write(f"head_ref={head_info['sha']}\n")
gha_outputs.write(f"base_ref={base_info['sha']}")
print(f"Head ref (PR #{head_info['pr_number']}): {head_info['sha']}")
print(f"Base ref (PR #{base_info['pr_number']}): {base_info['sha']}")
return head_info, base_info
print("Could not find or parse sufficient merge history")
return None, None
if __name__ == "__main__":
head_info, base_info = get_version_refs()

View file

@ -0,0 +1,60 @@
"""
This script updates a specific version's release notes section in CHANGELOG.md with new content.
The script:
1. Takes a version number, changelog path, and new content as input from environment variables
2. Finds the section in the changelog for the specified version
3. Replaces the content between the current version header and the next version header
(or end of file if it's the latest version) with the new content
4. Writes the updated changelog back to the file
Environment Variables:
CHANGELOG_PATH: Path to the changelog file (defaults to 'CHANGELOG.md')
VERSION: The version number to update notes for
PREV_VERSION: The previous version number (optional)
NEW_CONTENT: The new content to insert for this version
"""
#!/usr/bin/env python3
import os
CHANGELOG_PATH = os.environ.get("CHANGELOG_PATH", "CHANGELOG.md")
VERSION = os.environ['VERSION']
PREV_VERSION = os.environ.get("PREV_VERSION", "")
NEW_CONTENT = os.environ['NEW_CONTENT']
def overwrite_changelog_section(content: str):
"""Replace a specific version section in the changelog content.
Args:
content: The full changelog content as a string
Returns:
The updated changelog content with the new section
Example:
>>> content = "## 1.2.0\\nOld changes\\n## 1.1.0\\nOld changes"
>>> NEW_CONTENT = "New changes"
>>> overwrite_changelog_section(content)
'## 1.2.0\\nNew changes\\n## 1.1.0\\nOld changes'
"""
# Find the section for the specified version
version_pattern = f"## {VERSION}\n"
print(f"latest version: {VERSION}")
notes_start_index = content.find(version_pattern) + len(version_pattern)
print(f"prev_version: {PREV_VERSION}")
prev_version_pattern = f"## {PREV_VERSION}\n"
notes_end_index = content.find(prev_version_pattern, notes_start_index) if PREV_VERSION and prev_version_pattern in content else len(content)
return content[:notes_start_index] + f"{NEW_CONTENT}\n" + content[notes_end_index:]
with open(CHANGELOG_PATH, 'r') as f:
content = f.read()
new_changelog = overwrite_changelog_section(content)
print(new_changelog)
# Write back to CHANGELOG.md
with open(CHANGELOG_PATH, 'w') as f:
f.write(new_changelog)

64
.github/scripts/parse_changeset_changelog.py vendored Executable file
View file

@ -0,0 +1,64 @@
"""
This script extracts the release notes section for a specific version from CHANGELOG.md.
The script:
1. Takes a version number and changelog path as input from environment variables
2. Finds the section in the changelog for the specified version
3. Extracts the content between the current version header and the next version header
(or end of file if it's the latest version)
4. Outputs the extracted release notes to GITHUB_OUTPUT for use in creating GitHub releases
Environment Variables:
GITHUB_OUTPUT: Path to GitHub Actions output file
CHANGELOG_PATH: Path to the changelog file (defaults to 'CHANGELOG.md')
VERSION: The version number to extract notes for
"""
#!/usr/bin/env python3
import sys
import os
import subprocess
GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT")
CHANGELOG_PATH = os.environ.get("CHANGELOG_PATH", "CHANGELOG.md")
VERSION = os.environ['VERSION']
def parse_changelog_section(content: str):
"""Parse a specific version section from the changelog content.
Args:
content: The full changelog content as a string
Returns:
The formatted content for this version, or None if version not found
Example:
>>> content = "## 1.2.0\\nChanges\\n## 1.1.0\\nOld changes"
>>> parse_changelog_section(content)
'Changes\\n'
"""
# Find the section for the specified version
version_pattern = f"## {VERSION}\n"
print(f"latest version: {VERSION}")
notes_start_index = content.find(version_pattern) + len(version_pattern)
prev_version = subprocess.getoutput("git show origin/main:package.json | grep '\"version\":' | cut -d'\"' -f4")
print(f"prev_version: {prev_version}")
prev_version_pattern = f"## {prev_version}\n"
notes_end_index = content.find(prev_version_pattern, notes_start_index) if prev_version_pattern in content else len(content)
return content[notes_start_index:notes_end_index]
with open(CHANGELOG_PATH, 'r') as f:
content = f.read()
formatted_content = parse_changelog_section(content)
if not formatted_content:
print(f"Version {VERSION} not found in changelog", file=sys.stderr)
sys.exit(1)
print(formatted_content)
# Write the extracted release notes to GITHUB_OUTPUT
with open(GITHUB_OUTPUT, "a") as gha_output:
gha_output.write(f"release-notes<<EOF\n{formatted_content}\nEOF")

125
.github/scripts/release-notes-prompt.py vendored Normal file
View file

@ -0,0 +1,125 @@
import os
import subprocess
import json
import re
import tiktoken # type: ignore
from datetime import datetime;
from pytz import timezone
GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT")
BASE_REF = os.getenv("BASE_REF", "main")
HEAD_SHA = os.environ["HEAD_SHA"]
PR_TITLE = os.environ["PR_TITLE"]
PR_BODY = os.environ["PR_BODY"]
EXISTING_NOTES = os.environ.get("EXISTING_NOTES", "null")
MODEL_NAME = os.environ.get('MODEL_NAME', 'gpt-3.5-turbo-16k')
CUSTOM_PROMPT = os.environ.get('CUSTOM_PROMPT', '')
def extract_description_section(pr_body):
# Find content between ## Description and the next ## or end of text
description_match = re.search(r'## Description\s*\n(.*?)(?=\n##|$)', pr_body, re.DOTALL)
if description_match:
content = description_match.group(1).strip()
# Remove the comment line if it exists
comment_pattern = r'\[comment\]:.+?\n'
content = re.sub(comment_pattern, '', content)
return content.strip()
return ""
def extract_ellipsis_important(pr_body):
# Find content between <!-- ELLIPSIS_HIDDEN --> and <!-- ELLIPSIS_HIDDEN --> that contains [!IMPORTANT]
ellipsis_match = re.search(r'<!--\s*ELLIPSIS_HIDDEN\s*-->(.*?)<!--\s*ELLIPSIS_HIDDEN\s*-->', pr_body, re.DOTALL)
if ellipsis_match:
content = ellipsis_match.group(1).strip()
important_match = re.search(r'\[!IMPORTANT\](.*?)(?=\[!|$)', content, re.DOTALL)
if important_match:
important_text = important_match.group(1).strip()
important_text = re.sub(r'^-+\s*', '', important_text)
return important_text.strip()
return ""
def extract_coderabbit_summary(pr_body):
# Find content between ## Summary by CodeRabbit and the next ## or end of text
summary_match = re.search(r'## Summary by CodeRabbit\s*\n(.*?)(?=\n##|$)', pr_body, re.DOTALL)
return summary_match.group(1).strip() if summary_match else ""
def num_tokens_from_string(string: str, model_name: str) -> int:
"""
Calculate the number of tokens in a text string for a specific model.
Args:
string: The input text to count tokens for
model_name: Name of the OpenAI model to use for token counting
Returns:
int: Number of tokens in the input string
"""
encoding = tiktoken.encoding_for_model(model_name)
num_tokens = len(encoding.encode(string))
return num_tokens
def truncate_to_token_limit(text, max_tokens, model_name):
"""
Truncate text to fit within a maximum token limit for a specific model.
Args:
text: The input text to truncate
max_tokens: Maximum number of tokens allowed
model_name: Name of the OpenAI model to use for tokenization
Returns:
str: Truncated text that fits within the token limit
"""
encoding = tiktoken.encoding_for_model(model_name)
encoded = encoding.encode(text)
truncated = encoded[:max_tokens]
return encoding.decode(truncated)
# Extract sections and combine into PR_OVERVIEW
description = extract_description_section(PR_BODY)
important = extract_ellipsis_important(PR_BODY)
summary = extract_coderabbit_summary(PR_BODY)
PR_OVERVIEW = "\n\n".join(filter(None, [description, important, summary]))
# Get git information
base_sha = subprocess.getoutput(f"git rev-parse origin/{BASE_REF}") if BASE_REF == 'main' else BASE_REF
diff_overview = subprocess.getoutput(f"git diff {base_sha}..{HEAD_SHA} --name-status | awk '{{print $2}}' | sort | uniq -c | awk '{{print $2 \": \" $1 \" files changed\"}}'")
git_log = subprocess.getoutput(f"git log {base_sha}..{HEAD_SHA} --pretty=format:'%h - %s (%an)' --reverse | head -n 50")
git_diff = subprocess.getoutput(f"git diff {base_sha}..{HEAD_SHA} --minimal --abbrev --ignore-cr-at-eol --ignore-space-at-eol --ignore-space-change --ignore-all-space --ignore-blank-lines --unified=0 --diff-filter=ACDMRT")
max_tokens = 14000 # Reserve some tokens for the response
changes_summary = truncate_to_token_limit(diff_overview, 1000, MODEL_NAME)
git_logs = truncate_to_token_limit(git_log, 2000, MODEL_NAME)
changes_diff = truncate_to_token_limit(git_diff, max_tokens - num_tokens_from_string(changes_summary, MODEL_NAME) - num_tokens_from_string(git_logs, MODEL_NAME) - 1000, MODEL_NAME)
# Get today's existing changelog if any
existing_changelog = EXISTING_NOTES if EXISTING_NOTES != "null" else None
existing_changelog_text = f"\nAdditional context:\n{existing_changelog}" if existing_changelog else ""
TODAY = datetime.now(timezone('US/Eastern')).isoformat(sep=' ', timespec='seconds')
BASE_PROMPT = CUSTOM_PROMPT if CUSTOM_PROMPT else f"""Based on the following 'PR Information', please generate concise and informative release notes to be read by developers.
Format the release notes with markdown, and always use this structure: a descriptive and very short title (no more than 8 words) with heading level 2, a paragraph with a summary of changes (no header), and if applicable, sections for '🚀 New Features & Improvements', '🐛 Bugs Fixed' and '🔧 Other Updates', with heading level 3, skip respectively the sections if not applicable.
Finally include the following markdown comment with the PR merged date: <!-- PR_DATE: {TODAY} -->.
Avoid being repetitive and focus on the most important changes and their impact, discard any mention of version bumps/updates, changeset files, environment variables or syntax updates.
PR Information:"""
OPENAI_PROMPT = f"""{BASE_PROMPT}
Git log summary:
{changes_summary}
Commit Messages:
{git_logs}
PR Title:
{PR_TITLE}
PR Overview:
{PR_OVERVIEW}{existing_changelog_text}
Code Diff:
{json.dumps(changes_diff)}"""
print("OpenAI Prompt")
print("----------------------------------------------------------------")
print(OPENAI_PROMPT)
# Write the prompt to GITHUB_OUTPUT
with open(GITHUB_OUTPUT, "a") as outputs_file:
outputs_file.write(f"OPENAI_PROMPT<<EOF\n{OPENAI_PROMPT}\nEOF")

View file

@ -0,0 +1,215 @@
name: Changeset AI Release
run-name: Changeset AI Release ${{ github.actor != 'R00-B0T' && '- Create PR' || '- Approve & Release' }}
# This workflow automates the release process by:
# 1. Creating a version bump PR when changesets are merged to main
# 2. Using AI to generate release notes for the version bump PR
# 3. Auto-approving and merging the version bump PR
# 4. Creating a GitHub release with the AI-generated notes
on:
pull_request:
types: [closed, opened, synchronize, labeled]
env:
REPO_PATH: ${{ github.repository }}
GIT_REF: ${{ github.event.pull_request.head.sha }}
jobs:
# Job 1: Create version bump PR when changesets are merged to main
changeset-pr-version-bump:
if: >
github.event_name == 'pull_request' &&
github.event.pull_request.merged == true &&
github.event.pull_request.base.ref == 'main' &&
github.actor != 'R00-B0T'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Git Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ env.GIT_REF }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install Dependencies
run: npm install
# Check if there are any new changesets to process
- name: Check for changesets
id: check-changesets
run: |
NEW_CHANGESETS=$(find .changeset -name "*.md" ! -name "README.md" | wc -l | tr -d ' ')
echo "Changesets diff with previous version: $NEW_CHANGESETS"
echo "new_changesets=$NEW_CHANGESETS" >> $GITHUB_OUTPUT
# Create version bump PR using changesets/action if there are new changesets
- name: Changeset Pull Request
if: steps.check-changesets.outputs.new_changesets != '0'
id: changesets
uses: changesets/action@v1
with:
commit: "changeset version bump"
title: "Changeset version bump"
version: npm run version-packages # This performs the changeset version bump
env:
GITHUB_TOKEN: ${{ secrets.CROSS_REPO_ACCESS_TOKEN }}
# Job 2: Process version bump PR created by R00-B0T
changeset-pr-approve-merge:
name: Auto approve and merge Bump version PRs
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
if: >
github.event_name == 'pull_request' &&
github.event.pull_request.base.ref == 'main' &&
github.actor == 'R00-B0T' &&
contains(github.event.pull_request.title, 'Changeset version bump')
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
token: ${{ secrets.CROSS_REPO_ACCESS_TOKEN }}
fetch-depth: 0
ref: ${{ env.GIT_REF }}
# Get current and previous versions for changelog processing
- name: Get version
id: get_version
run: |
VERSION=$(git show HEAD:package.json | jq -r '.version')
echo "version=$VERSION" >> $GITHUB_OUTPUT
PREV_VERSION=$(git show origin/main:package.json | jq -r '.version')
echo "prev_version=$PREV_VERSION" >> $GITHUB_OUTPUT
echo "version=$VERSION"
echo "prev_version=$PREV_VERSION"
# Get previous version refs, GITHUB_OUTPUT: 'BASE_REF' and 'HEAD_REF'
- name: Get Previous Version Refs
id: version_refs
run: python .github/scripts/get_prev_version_refs.py
# Generate release notes using OpenAI if not already edited, GITHUB_OUTPUT: 'RELEASE_NOTES' and 'OPENAI_PROMPT'
- name: AI Release Notes
if: ${{ !contains(github.event.pull_request.labels.*.name, 'openai-edited') }}
uses: ./.github/actions/ai-release-notes
id: ai_release_notes
with:
GHA_PAT: ${{ secrets.CROSS_REPO_ACCESS_TOKEN }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
model_name: gpt-4o-mini
repo_path: ${{ env.REPO_PATH }}
base_ref: ${{ steps.version_refs.outputs.base_ref }}
head_ref: ${{ steps.version_refs.outputs.head_ref }}
# Update CHANGELOG.md with AI-generated notes
- name: Update Changeset Changelog
if: ${{ !contains(github.event.pull_request.labels.*.name, 'openai-edited') }}
env:
VERSION: ${{ steps.get_version.outputs.version }}
PREV_VERSION: ${{ steps.get_version.outputs.prev_version }}
NEW_CONTENT: ${{ steps.ai_release_notes.outputs.RELEASE_NOTES }}
run: python .github/scripts/overwrite_changeset_changelog.py
# Commit and push changelog updates
- name: Push Changelog updates
if: ${{ !contains(github.event.pull_request.labels.*.name, 'openai-edited') }}
run: |
git config user.name "R00-B0T"
git config user.email github-actions@github.com
git status
echo "Updating changelog.md..."
git add CHANGELOG.md
git commit -m "Updating changeset changelog"
echo "--------------------------------------------------------------------------------"
echo "Pushing to remote..."
echo "--------------------------------------------------------------------------------"
git push
# Add label to indicate OpenAI has processed this PR
- name: Add openai-edited label
if: ${{ !contains(github.event.pull_request.labels.*.name, 'openai-edited') }}
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['openai-edited']
});
# Auto-approve PR once OpenAI has processed it
- name: Auto approve PR
if: contains(github.event.pull_request.labels.*.name, 'openai-edited')
uses: hmarr/auto-approve-action@v4
with:
review-message: "I'm approving since it's a bump version PR"
# Enable auto-merge for the PR
- name: Enable automerge on PR
if: contains(github.event.pull_request.labels.*.name, 'openai-edited')
run: gh pr merge --squash --auto ${{ github.event.pull_request.number }}
env:
GH_TOKEN: ${{ secrets.CROSS_REPO_ACCESS_TOKEN }}
# Job 3: Create GitHub release after version bump PR is merged
github-release:
runs-on: ubuntu-latest
if: >
github.event_name == 'pull_request' &&
github.event.pull_request.merged == true &&
github.event.pull_request.base.ref == 'main' &&
github.actor == 'R00-B0T' &&
contains(github.event.pull_request.title, 'Changeset version bump')
permissions:
contents: write
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Get version
id: get_version
run: |
VERSION=$(git show HEAD:package.json | jq -r '.version')
echo "version=$VERSION" >> $GITHUB_OUTPUT
# Extract release notes from CHANGELOG.md, GITHUB_OUTPUT: 'release-notes'
- name: Parse CHANGELOG.md
id: changelog
env:
CHANGELOG_PATH: CHANGELOG.md
VERSION: ${{ steps.get_version.outputs.version }}
run: python .github/scripts/parse_changeset_changelog.py
# Create GitHub release with extracted notes
- name: Create or Update Release
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ steps.get_version.outputs.version }}
name: Release v${{ steps.get_version.outputs.version }}
draft: false
prerelease: false
append_body: false
make_latest: true
body: ${{ steps.changelog.outputs.release-notes }}

View file

@ -8,6 +8,7 @@ jobs:
publish-extension:
if: ${{ github.actor == 'R00-B0T'}}
runs-on: ubuntu-latest
if: ${{ github.actor == 'R00-B0T'}}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3

2
.gitignore vendored
View file

@ -9,6 +9,6 @@ node_modules
bin
roo-cline-*.vsix
# Prompts
# Local prompts
prompts
.clinerules

9
.husky/pre-commit Normal file
View file

@ -0,0 +1,9 @@
branch="$(git rev-parse --abbrev-ref HEAD)"
if [ "$branch" = "main" ]; then
echo "You can't commit directly to main - please check out a branch."
exit 1
fi
npx lint-staged
npm run compile

17
.husky/pre-push Normal file
View file

@ -0,0 +1,17 @@
branch="$(git rev-parse --abbrev-ref HEAD)"
if [ "$branch" = "main" ]; then
echo "You can't push directly to main - please check out a branch."
exit 1
fi
npm run compile
# Check for new changesets
NEW_CHANGESETS=$(find .changeset -name "*.md" ! -name "README.md" | wc -l | tr -d ' ')
echo "Changeset files: $NEW_CHANGESETS"
if [ "$NEW_CHANGESETS" == "0" ]; then
echo "-------------------------------------------------------------------------------------"
echo "Changes detected. Please run 'npm run changeset' to create a changeset if applicable."
echo "-------------------------------------------------------------------------------------"
fi

View file

@ -1,72 +1,86 @@
# Roo Cline Changelog
## 2.1.21
### Patch Changes
- 8dbd019: Larger Promp Text Input
## [2.1.20]
- Add Gemini 2.0
## [2.1.19]
- Better error handling for diff editing
## [2.1.18]
- Diff editing bugfix to handle Windows line endings
- Diff editing bugfix to handle Windows line endings
## [2.1.17]
- Switch to search/replace diffs in experimental diff editing mode
- Switch to search/replace diffs in experimental diff editing mode
## [2.1.16]
- Allow copying prompts from the history screen
- Allow copying prompts from the history screen
## [2.1.15]
- Incorporate dbasclpy's [PR](https://github.com/RooVetGit/Roo-Cline/pull/54) to add support for gemini-exp-1206
- Make it clear that diff editing is very experimental
- Incorporate dbasclpy's [PR](https://github.com/RooVetGit/Roo-Cline/pull/54) to add support for gemini-exp-1206
- Make it clear that diff editing is very experimental
## [2.1.14]
- Fix bug where diffs were not being applied correctly and try Aider's [unified diff prompt](https://github.com/Aider-AI/aider/blob/3995accd0ca71cea90ef76d516837f8c2731b9fe/aider/coders/udiff_prompts.py#L75-L105)
- If diffs are enabled, automatically reject write_to_file commands that lead to truncated output
- Fix bug where diffs were not being applied correctly and try Aider's [unified diff prompt](https://github.com/Aider-AI/aider/blob/3995accd0ca71cea90ef76d516837f8c2731b9fe/aider/coders/udiff_prompts.py#L75-L105)
- If diffs are enabled, automatically reject write_to_file commands that lead to truncated output
## [2.1.13]
- Fix https://github.com/RooVetGit/Roo-Cline/issues/50 where sound effects were not respecting settings
- Fix https://github.com/RooVetGit/Roo-Cline/issues/50 where sound effects were not respecting settings
## [2.1.12]
- Incorporate JoziGila's [PR](https://github.com/cline/cline/pull/158) to add support for editing through diffs
- Incorporate JoziGila's [PR](https://github.com/cline/cline/pull/158) to add support for editing through diffs
## [2.1.11]
- Incorporate lloydchang's [PR](https://github.com/RooVetGit/Roo-Cline/pull/42) to add support for OpenRouter compression
- Incorporate lloydchang's [PR](https://github.com/RooVetGit/Roo-Cline/pull/42) to add support for OpenRouter compression
## [2.1.10]
- Incorporate HeavenOSK's [PR](https://github.com/cline/cline/pull/818) to add sound effects to Cline
- Incorporate HeavenOSK's [PR](https://github.com/cline/cline/pull/818) to add sound effects to Cline
## [2.1.9]
- Add instructions for using .clinerules on the settings screen
- Add instructions for using .clinerules on the settings screen
## [2.1.8]
- Roo Cline now allows configuration of which commands are allowed without approval!
- Roo Cline now allows configuration of which commands are allowed without approval!
## [2.1.7]
- Updated extension icon and metadata
- Updated extension icon and metadata
## [2.1.6]
- Roo Cline now runs in all VSCode-compatible editors
- Roo Cline now runs in all VSCode-compatible editors
## [2.1.5]
- Fix bug in browser action approval
- Fix bug in browser action approval
## [2.1.4]
- Roo Cline now can run side-by-side with Cline
- Roo Cline now can run side-by-side with Cline
## [2.1.3]
- Roo Cline now allows browser actions without approval when `alwaysAllowBrowser` is true
- Roo Cline now allows browser actions without approval when `alwaysAllowBrowser` is true
## [2.1.2]
- Support for auto-approval of write operations and command execution
- Support for .clinerules custom instructions
- Support for auto-approval of write operations and command execution
- Support for .clinerules custom instructions

View file

@ -14,11 +14,12 @@ Here's an example of Roo-Cline autonomously creating a snake game with "Always a
https://github.com/user-attachments/assets/c2bb31dc-e9b2-4d73-885d-17f1471a4987
### Installation
If you want to install the latest extension from the Marketplace, just search for "Roo Cline" in your VSCode-compatible editor's Extensions panel (Cmd/Ctrl+Shift+X).
## Contributing
To contribute to the project, start by exploring [open issues](https://github.com/RooVetGit/Roo-Cline/issues) or checking our [feature request board](https://github.com/cline/cline/discussions/categories/feature-requests?discussions_q=is%3Aopen+category%3A%22Feature+Requests%22+sort%3Atop). We'd also love to have you join our [Discord](https://discord.gg/cline) to share ideas and connect with other contributors.
<details>
<summary>Local Setup</summary>
### Testing Builds
1. Install dependencies:
```bash
npm run install:all
@ -40,7 +41,12 @@ If you want to install the latest extension from the Marketplace, just search fo
# Ex: code --install-extension bin/roo-cline-2.0.1.vsix
```
### Publishing
5. Launch by pressing `F5` (or `Run`->`Start Debugging`) to open a new VSCode window with the extension loaded. (You may need to install the [esbuild problem matchers extension](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers) if you run into issues building the project.)
</details>
<details>
<summary>Publishing</summary>
We use [changesets](https://github.com/changesets/changesets) for versioning and publishing this package. To make changes:
1. Create a PR with your changes
@ -54,34 +60,15 @@ Once your merge is successful:
- The release workflow will automatically create a new "Changeset version bump" PR
- This PR will:
- Update the version based on your changeset
- Update the CHANGELOG.md file
- Update the `CHANGELOG.md` file
- Create a git tag
- The PR will be automatically approved and merged
- A new version will be published
- A new version and git release will be published
### Browser Actions
</details>
The browser action tool allows Cline to interact with web pages through a Puppeteer-controlled browser. This can be used in two modes:
#### Interactive Mode
- Triggered by adding the string `interactive mode` to your prompt, it also requires the string `(browserPort = 7333)`, where the port number can be modified.
- Requires a browser instance running on the specified port, ex: `/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --remote-debugging-port=7333`
- Useful for debugging and development scenarios where you need to interact with a running browser
#### Non-Interactive Mode
- Default mode when no `interactive mode` string is present in your prompt
- Launches a new browser instance for each session
- Better suited for quick simple browser tasks, ex: testing a URL
The browser action, in both modes, supports operations like:
- Launching a browser at a specified URL
- Clicking elements at specific coordinates
- Typing text
- Scrolling the page
- Capturing screenshots and console logs
- Closing the browser
Each browser action provides feedback through screenshots and console logs, allowing for detailed interaction tracking and debugging.
## Stay Updated!
Subscribe to our [Github releases](https://github.com/RooVetGit/Roo-Cline/releases) to keep up with the latest updates! You can also view our [CHANGELOG.md](Roo-Cline/CHANGELOG.md) for more details.
---
@ -181,30 +168,3 @@ Try asking Cline to "test the app", and watch as he runs a command like `npm run
- **`@problems`:** Add workspace errors and warnings ('Problems' panel) for Cline to fix
- **`@file`:** Adds a file's contents so you don't have to waste API requests approving read file (+ type to search files)
- **`@folder`:** Adds folder's files all at once to speed up your workflow even more
## Contributing
To contribute to the project, start by exploring [open issues](https://github.com/cline/cline/issues) or checking our [feature request board](https://github.com/cline/cline/discussions/categories/feature-requests?discussions_q=is%3Aopen+category%3A%22Feature+Requests%22+sort%3Atop). We'd also love to have you join our [Discord](https://discord.gg/cline) to share ideas and connect with other contributors. If you're interested in joining the team, check out our [careers page](https://cline.bot/join-us)!
<details>
<summary>Local Development Instructions</summary>
1. Clone the repository _(Requires [git-lfs](https://git-lfs.com/))_:
```bash
git clone https://github.com/cline/cline.git
```
2. Open the project in VSCode:
```bash
code cline
```
3. Install the necessary dependencies for the extension and webview-gui:
```bash
npm run install:all
```
4. Launch by pressing `F5` (or `Run`->`Start Debugging`) to open a new VSCode window with the extension loaded. (You may need to install the [esbuild problem matchers extension](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers) if you run into issues building the project.)
</details>
## License
[Apache 2.0 © 2024 Cline Bot Inc.](./LICENSE)

609
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "roo-cline",
"version": "2.1.18",
"version": "2.1.21",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "roo-cline",
"version": "2.1.18",
"version": "2.1.21",
"dependencies": {
"@anthropic-ai/bedrock-sdk": "^0.10.2",
"@anthropic-ai/sdk": "^0.26.0",
@ -42,7 +42,7 @@
"web-tree-sitter": "^0.22.6"
},
"devDependencies": {
"@changesets/cli": "^2.27.1",
"@changesets/cli": "^2.27.10",
"@types/diff": "^5.2.1",
"@types/jest": "^29.5.14",
"@types/mocha": "^10.0.7",
@ -53,7 +53,9 @@
"@vscode/test-electron": "^2.4.0",
"esbuild": "^0.24.0",
"eslint": "^8.57.0",
"husky": "^9.1.7",
"jest": "^29.7.0",
"lint-staged": "^15.2.11",
"npm-run-all": "^4.1.5",
"ts-jest": "^29.2.5",
"typescript": "^5.4.5"
@ -7332,6 +7334,45 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-truncate": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz",
"integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==",
"dev": true,
"dependencies": {
"slice-ansi": "^5.0.0",
"string-width": "^7.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-truncate/node_modules/emoji-regex": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
"dev": true
},
"node_modules/cli-truncate/node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dev": true,
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@ -7443,6 +7484,12 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/colorette": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"dev": true
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@ -8077,6 +8124,18 @@
"node": ">=6"
}
},
"node_modules/environment": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
"integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@ -8505,6 +8564,12 @@
"node": ">=6"
}
},
"node_modules/eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"dev": true
},
"node_modules/execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
@ -9009,6 +9074,18 @@
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-east-asian-width": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz",
"integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-intrinsic": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
@ -9382,6 +9459,21 @@
"ms": "^2.0.0"
}
},
"node_modules/husky": {
"version": "9.1.7",
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
"integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
"dev": true,
"bin": {
"husky": "bin.js"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/typicode"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@ -11022,6 +11114,18 @@
"immediate": "~3.0.5"
}
},
"node_modules/lilconfig": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
"dev": true,
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antonk52"
}
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@ -11036,6 +11140,257 @@
"uc.micro": "^1.0.1"
}
},
"node_modules/lint-staged": {
"version": "15.2.11",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.11.tgz",
"integrity": "sha512-Ev6ivCTYRTGs9ychvpVw35m/bcNDuBN+mnTeObCL5h+boS5WzBEC6LHI4I9F/++sZm1m+J2LEiy0gxL/R9TBqQ==",
"dev": true,
"dependencies": {
"chalk": "~5.3.0",
"commander": "~12.1.0",
"debug": "~4.4.0",
"execa": "~8.0.1",
"lilconfig": "~3.1.3",
"listr2": "~8.2.5",
"micromatch": "~4.0.8",
"pidtree": "~0.6.0",
"string-argv": "~0.3.2",
"yaml": "~2.6.1"
},
"bin": {
"lint-staged": "bin/lint-staged.js"
},
"engines": {
"node": ">=18.12.0"
},
"funding": {
"url": "https://opencollective.com/lint-staged"
}
},
"node_modules/lint-staged/node_modules/chalk": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
"dev": true,
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/lint-staged/node_modules/commander": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"dev": true,
"engines": {
"node": ">=18"
}
},
"node_modules/lint-staged/node_modules/execa": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
"integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
"dev": true,
"dependencies": {
"cross-spawn": "^7.0.3",
"get-stream": "^8.0.1",
"human-signals": "^5.0.0",
"is-stream": "^3.0.0",
"merge-stream": "^2.0.0",
"npm-run-path": "^5.1.0",
"onetime": "^6.0.0",
"signal-exit": "^4.1.0",
"strip-final-newline": "^3.0.0"
},
"engines": {
"node": ">=16.17"
},
"funding": {
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/lint-staged/node_modules/get-stream": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
"integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
"dev": true,
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lint-staged/node_modules/human-signals": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
"integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
"dev": true,
"engines": {
"node": ">=16.17.0"
}
},
"node_modules/lint-staged/node_modules/is-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
"integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
"dev": true,
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lint-staged/node_modules/mimic-fn": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lint-staged/node_modules/npm-run-path": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
"integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
"dev": true,
"dependencies": {
"path-key": "^4.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lint-staged/node_modules/onetime": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
"integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
"dev": true,
"dependencies": {
"mimic-fn": "^4.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lint-staged/node_modules/path-key": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lint-staged/node_modules/pidtree": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz",
"integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==",
"dev": true,
"bin": {
"pidtree": "bin/pidtree.js"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/lint-staged/node_modules/strip-final-newline": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
"integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/listr2": {
"version": "8.2.5",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz",
"integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==",
"dev": true,
"dependencies": {
"cli-truncate": "^4.0.0",
"colorette": "^2.0.20",
"eventemitter3": "^5.0.1",
"log-update": "^6.1.0",
"rfdc": "^1.4.1",
"wrap-ansi": "^9.0.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/listr2/node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/listr2/node_modules/emoji-regex": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
"dev": true
},
"node_modules/listr2/node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dev": true,
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/listr2/node_modules/wrap-ansi": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
"integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
"dev": true,
"dependencies": {
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/load-json-file": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
@ -11122,6 +11477,169 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
"integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
"dev": true,
"dependencies": {
"ansi-escapes": "^7.0.0",
"cli-cursor": "^5.0.0",
"slice-ansi": "^7.1.0",
"strip-ansi": "^7.1.0",
"wrap-ansi": "^9.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update/node_modules/ansi-escapes": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz",
"integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==",
"dev": true,
"dependencies": {
"environment": "^1.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update/node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/log-update/node_modules/cli-cursor": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
"integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
"dev": true,
"dependencies": {
"restore-cursor": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update/node_modules/emoji-regex": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
"dev": true
},
"node_modules/log-update/node_modules/is-fullwidth-code-point": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz",
"integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==",
"dev": true,
"dependencies": {
"get-east-asian-width": "^1.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update/node_modules/onetime": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
"integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
"dev": true,
"dependencies": {
"mimic-function": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update/node_modules/restore-cursor": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
"integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
"dev": true,
"dependencies": {
"onetime": "^7.0.0",
"signal-exit": "^4.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update/node_modules/slice-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz",
"integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==",
"dev": true,
"dependencies": {
"ansi-styles": "^6.2.1",
"is-fullwidth-code-point": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
"node_modules/log-update/node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dev": true,
"dependencies": {
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update/node_modules/wrap-ansi": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
"integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
"dev": true,
"dependencies": {
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/lop": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/lop/-/lop-0.4.2.tgz",
@ -11319,6 +11837,18 @@
"node": ">=6"
}
},
"node_modules/mimic-function": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
"integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
@ -12283,9 +12813,9 @@
"dev": true
},
"node_modules/package-manager-detector": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.5.tgz",
"integrity": "sha512-3dS7y28uua+UDbRCLBqltMBrbI+A5U2mI9YuxHRxIWYmLj3DwntEBmERYzIAQ4DMeuCUOBSak7dBHHoXKpOTYQ==",
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.7.tgz",
"integrity": "sha512-g4+387DXDKlZzHkP+9FLt8yKj8+/3tOkPv7DVTJGGRm00RkEWgqbFstX1mXJ4M0VDYhUqsTOiISqNOJnhAu3PQ==",
"dev": true
},
"node_modules/pako": {
@ -13230,6 +13760,12 @@
"node": ">=0.10.0"
}
},
"node_modules/rfdc": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"dev": true
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@ -13593,6 +14129,46 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/slice-ansi": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
"integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^6.0.0",
"is-fullwidth-code-point": "^4.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
"node_modules/slice-ansi/node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/slice-ansi/node_modules/is-fullwidth-code-point": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
"integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
@ -13756,6 +14332,15 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"node_modules/string-argv": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
"integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
"dev": true,
"engines": {
"node": ">=0.6.19"
}
},
"node_modules/string-length": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
@ -15243,6 +15828,18 @@
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
},
"node_modules/yaml": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
"integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==",
"dev": true,
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",

View file

@ -3,7 +3,7 @@
"displayName": "Roo Cline",
"description": "A fork of Cline, an autonomous coding agent, with some added experimental configuration and automation features.",
"publisher": "RooVeterinaryInc",
"version": "2.1.18",
"version": "2.1.21",
"icon": "assets/icons/rocket.png",
"galleryBanner": {
"color": "#617A91",
@ -152,14 +152,14 @@
"publish": "npm run build && changeset publish && npm install --package-lock-only",
"version-packages": "changeset version && npm install --package-lock-only",
"vscode:prepublish": "npm run package",
"vsix": "npx vsce package --out bin",
"vsix": "mkdir -p bin && npx vsce package --out bin",
"watch": "npm-run-all -p watch:*",
"watch:esbuild": "node esbuild.js --watch",
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
"watch-tests": "tsc -p . -w --outDir out"
},
"devDependencies": {
"@changesets/cli": "^2.27.1",
"@changesets/cli": "^2.27.10",
"@types/diff": "^5.2.1",
"@types/jest": "^29.5.14",
"@types/mocha": "^10.0.7",
@ -170,7 +170,9 @@
"@vscode/test-electron": "^2.4.0",
"esbuild": "^0.24.0",
"eslint": "^8.57.0",
"husky": "^9.1.7",
"jest": "^29.7.0",
"lint-staged": "^15.2.11",
"npm-run-all": "^4.1.5",
"ts-jest": "^29.2.5",
"typescript": "^5.4.5"
@ -208,5 +210,10 @@
"turndown": "^7.2.0",
"vsce": "^2.15.0",
"web-tree-sitter": "^0.22.6"
},
"lint-staged": {
"src/**/*.{ts,tsx}": [
"npx eslint -c .eslintrc.json"
]
}
}

View file

@ -17,7 +17,8 @@ export class OpenAiHandler implements ApiHandler {
constructor(options: ApiHandlerOptions) {
this.options = options
// Azure API shape slightly differs from the core API shape: https://github.com/openai/openai-node?tab=readme-ov-file#microsoft-azure-openai
if (this.options.openAiBaseUrl?.toLowerCase().includes("azure.com")) {
const urlHost = new URL(this.options.openAiBaseUrl ?? "").host;
if (urlHost === "azure.com" || urlHost.endsWith(".azure.com")) {
this.client = new AzureOpenAI({
baseURL: this.options.openAiBaseUrl,
apiKey: this.options.openAiApiKey,

View file

@ -1234,12 +1234,12 @@ export class Cline {
pushToolResult(await this.sayAndCreateMissingParamError("apply_diff", "diff"))
break
}
this.consecutiveMistakeCount = 0
const absolutePath = path.resolve(cwd, relPath)
const fileExists = await fileExistsAtPath(absolutePath)
if (!fileExists) {
this.consecutiveMistakeCount++
await this.say("error", `File does not exist at path: ${absolutePath}`)
pushToolResult(`Error: File does not exist at path: ${absolutePath}`)
break
@ -1250,11 +1250,14 @@ export class Cline {
// Apply the diff to the original content
let newContent = this.diffStrategy?.applyDiff(originalContent, diffContent) ?? false
if (newContent === false) {
await this.say("error", `Error applying diff to file: ${absolutePath}`)
pushToolResult(`Error applying diff to file: ${absolutePath}`)
this.consecutiveMistakeCount++
await this.say("error", `Unable to apply diff to file - contents are out of sync: ${absolutePath}`)
pushToolResult(`Error applying diff to file: ${absolutePath} - contents are out of sync. Try re-reading the relevant lines of the file and applying the diff again.`)
break
}
this.consecutiveMistakeCount = 0
// Show diff view before asking for approval
this.diffViewProvider.editType = "modify"
await this.diffViewProvider.open(relPath);

View file

@ -124,17 +124,18 @@ export class DiffViewProvider {
edit.delete(document.uri, new vscode.Range(this.streamedLines.length, 0, document.lineCount, 0))
await vscode.workspace.applyEdit(edit)
}
// Add empty last line if original content had one
// Preserve empty last line if original content had one
const hasEmptyLastLine = this.originalContent?.endsWith("\n")
if (hasEmptyLastLine) {
const accumulatedLines = accumulatedContent.split("\n")
if (accumulatedLines[accumulatedLines.length - 1] !== "") {
accumulatedContent += "\n"
}
if (hasEmptyLastLine && !accumulatedContent.endsWith("\n")) {
accumulatedContent += "\n"
}
// Clear all decorations at the end (before applying final edit)
this.fadedOverlayController.clear()
this.activeLineController.clear()
// Apply the final content
const finalEdit = new vscode.WorkspaceEdit()
finalEdit.replace(document.uri, new vscode.Range(0, 0, document.lineCount, 0), accumulatedContent)
await vscode.workspace.applyEdit(finalEdit)
// Clear all decorations at the end (after applying final edit)
this.fadedOverlayController.clear()
this.activeLineController.clear()
}
}
@ -351,4 +352,4 @@ export class DiffViewProvider {
this.streamedLines = []
this.preDiagnostics = []
}
}
}

View file

@ -0,0 +1,100 @@
import { DiffViewProvider } from '../DiffViewProvider';
import * as vscode from 'vscode';
// Mock vscode
jest.mock('vscode', () => ({
workspace: {
applyEdit: jest.fn(),
},
window: {
createTextEditorDecorationType: jest.fn(),
},
WorkspaceEdit: jest.fn().mockImplementation(() => ({
replace: jest.fn(),
delete: jest.fn(),
})),
Range: jest.fn(),
Position: jest.fn(),
Selection: jest.fn(),
TextEditorRevealType: {
InCenter: 2,
},
}));
// Mock DecorationController
jest.mock('../DecorationController', () => ({
DecorationController: jest.fn().mockImplementation(() => ({
setActiveLine: jest.fn(),
updateOverlayAfterLine: jest.fn(),
clear: jest.fn(),
})),
}));
describe('DiffViewProvider', () => {
let diffViewProvider: DiffViewProvider;
const mockCwd = '/mock/cwd';
let mockWorkspaceEdit: { replace: jest.Mock; delete: jest.Mock };
beforeEach(() => {
jest.clearAllMocks();
mockWorkspaceEdit = {
replace: jest.fn(),
delete: jest.fn(),
};
(vscode.WorkspaceEdit as jest.Mock).mockImplementation(() => mockWorkspaceEdit);
diffViewProvider = new DiffViewProvider(mockCwd);
// Mock the necessary properties and methods
(diffViewProvider as any).relPath = 'test.txt';
(diffViewProvider as any).activeDiffEditor = {
document: {
uri: { fsPath: `${mockCwd}/test.txt` },
getText: jest.fn(),
lineCount: 10,
},
selection: {
active: { line: 0, character: 0 },
anchor: { line: 0, character: 0 },
},
edit: jest.fn().mockResolvedValue(true),
revealRange: jest.fn(),
};
(diffViewProvider as any).activeLineController = { setActiveLine: jest.fn(), clear: jest.fn() };
(diffViewProvider as any).fadedOverlayController = { updateOverlayAfterLine: jest.fn(), clear: jest.fn() };
});
describe('update method', () => {
it('should preserve empty last line when original content has one', async () => {
(diffViewProvider as any).originalContent = 'Original content\n';
await diffViewProvider.update('New content', true);
expect(mockWorkspaceEdit.replace).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
'New content\n'
);
});
it('should not add extra newline when accumulated content already ends with one', async () => {
(diffViewProvider as any).originalContent = 'Original content\n';
await diffViewProvider.update('New content\n', true);
expect(mockWorkspaceEdit.replace).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
'New content\n'
);
});
it('should not add newline when original content does not end with one', async () => {
(diffViewProvider as any).originalContent = 'Original content';
await diffViewProvider.update('New content', true);
expect(mockWorkspaceEdit.replace).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
'New content'
);
});
});
});

View file

@ -286,6 +286,14 @@ export const geminiModels = {
inputPrice: 0,
outputPrice: 0,
},
"gemini-2.0-flash-exp": {
maxTokens: 8192,
contextWindow: 1_048_576,
supportsImages: true,
supportsPromptCache: false,
inputPrice: 0,
outputPrice: 0,
},
} as const satisfies Record<string, ModelInfo>
// OpenAI Native

View file

@ -13,6 +13,8 @@ import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
import ContextMenu from "./ContextMenu"
import Thumbnails from "../common/Thumbnails"
declare const vscode: any;
interface ChatTextAreaProps {
inputValue: string
setInputValue: (value: string) => void
@ -427,7 +429,62 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
opacity: textAreaDisabled ? 0.5 : 1,
position: "relative",
display: "flex",
}}>
}}
onDrop={async (e) => {
console.log("onDrop called")
e.preventDefault()
const files = Array.from(e.dataTransfer.files)
const text = e.dataTransfer.getData("text")
if (text) {
const newValue =
inputValue.slice(0, cursorPosition) + text + inputValue.slice(cursorPosition)
setInputValue(newValue)
const newCursorPosition = cursorPosition + text.length
setCursorPosition(newCursorPosition)
setIntendedCursorPosition(newCursorPosition)
return
}
const acceptedTypes = ["png", "jpeg", "webp"]
const imageFiles = files.filter((file) => {
const [type, subtype] = file.type.split("/")
return type === "image" && acceptedTypes.includes(subtype)
})
if (!shouldDisableImages && imageFiles.length > 0) {
const imagePromises = imageFiles.map((file) => {
return new Promise<string | null>((resolve) => {
const reader = new FileReader()
reader.onloadend = () => {
if (reader.error) {
console.error("Error reading file:", reader.error)
resolve(null)
} else {
const result = reader.result
console.log("File read successfully", result)
resolve(typeof result === "string" ? result : null)
}
}
reader.readAsDataURL(file)
})
})
const imageDataArray = await Promise.all(imagePromises)
const dataUrls = imageDataArray.filter((dataUrl): dataUrl is string => dataUrl !== null)
if (dataUrls.length > 0) {
setSelectedImages((prevImages) => [...prevImages, ...dataUrls].slice(0, MAX_IMAGES_PER_MESSAGE))
if (typeof vscode !== 'undefined') {
vscode.postMessage({
type: 'draggedImages',
dataUrls: dataUrls
})
}
} else {
console.warn("No valid images were processed")
}
}
}}
onDragOver={(e) => {
e.preventDefault()
}}
>
{showContextMenu && (
<div ref={contextMenuContainerRef}>
<ContextMenu
@ -508,7 +565,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
onHeightChange?.(height)
}}
placeholder={placeholderText}
maxRows={10}
minRows={2}
maxRows={20}
autoFocus={true}
style={{
width: "100%",
@ -523,7 +581,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
resize: "none",
overflowX: "hidden",
overflowY: "scroll",
scrollbarWidth: "none",
// Since we have maxRows, when text is long enough it starts to overflow the bottom padding, appearing behind the thumbnails. To fix this, we use a transparent border to push the text up instead. (https://stackoverflow.com/questions/42631947/maintaining-a-padding-inside-of-text-area/52538410#52538410)
// borderTop: "9px solid transparent",
borderLeft: 0,
@ -560,11 +617,11 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
<div
style={{
position: "absolute",
right: 23,
right: 28,
display: "flex",
alignItems: "flex-center",
alignItems: "flex-end",
height: textAreaBaseHeight || 31,
bottom: 9.5, // should be 10 but doesnt look good on mac
bottom: 18,
zIndex: 2,
}}>
<div style={{ display: "flex", flexDirection: "row", alignItems: "center" }}>

View file

@ -689,7 +689,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
useEvent("wheel", handleWheel, window, { passive: true }) // passive improves scrolling performance
const placeholderText = useMemo(() => {
const text = task ? "Type a message (@ to add context)..." : "Type your task here (@ to add context)..."
const text = task ? "Type a message...\n(@ to add context, hold shift to drag in images)" : "Type your task here...\n(@ to add context, hold shift to drag in images)"
return text
}, [task])

View file

@ -462,10 +462,13 @@ export const highlight = (
let i: number
for (i = 0; i < pathValue.length - 1; i++) {
if (pathValue[i] === "__proto__" || pathValue[i] === "constructor") return
obj = obj[pathValue[i]] as Record<string, any>
}
obj[pathValue[i]] = value
if (pathValue[i] !== "__proto__" && pathValue[i] !== "constructor") {
obj[pathValue[i]] = value
}
}
// Function to merge overlapping regions