mirror of
https://github.com/cline/cline.git
synced 2026-09-08 22:13:11 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f0579a265 | ||
|
|
c70e14c298 | ||
|
|
dc97689836 | ||
|
|
2992baaaea | ||
|
|
fd13081109 | ||
|
|
1ee733103a | ||
|
|
1dc3ba5f40 | ||
|
|
b4b99bc331 | ||
|
|
b29c512aa2 | ||
|
|
11dd56ae60 | ||
|
|
1a4bae7f0d | ||
|
|
b672eeb307 | ||
|
|
c69b515345 | ||
|
|
9467583938 | ||
|
|
838a3ae10f | ||
|
|
3daca3d68f | ||
|
|
71a9ed1d1e | ||
|
|
3fc300273d | ||
|
|
d93a08af5d | ||
|
|
45ee2c3e1c | ||
|
|
5c98a3d4af | ||
|
|
24666fed30 | ||
|
|
7afae36ab7 | ||
|
|
e5d86ffb8d | ||
|
|
d7d3f44a5d | ||
|
|
e6f6d754b2 | ||
|
|
92eaa53db3 | ||
|
|
ace755c1d0 | ||
|
|
43d23adc34 | ||
|
|
b23ecca086 | ||
|
|
fd0b3a18f7 | ||
|
|
d324afdf14 | ||
|
|
cf2ab16538 | ||
|
|
0e4000e1f2 | ||
|
|
c1137277dc | ||
|
|
31a6da944a | ||
|
|
998aa8b7eb | ||
|
|
7842b07d97 | ||
|
|
fc82d693ac | ||
|
|
2b9a1e2c7e | ||
|
|
890d340d7f | ||
|
|
e356294754 | ||
|
|
181a5f299d | ||
|
|
3a63dfc31f | ||
|
|
ecaaaaf4af | ||
|
|
86e743cd99 |
@@ -18,6 +18,7 @@ vsc-extension-quickstart.md
|
||||
demo.gif
|
||||
.nvmrc
|
||||
.gitattributes
|
||||
.prettierignore
|
||||
|
||||
# Ignore all webview-ui files except the build directory (https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/frameworks/hello-world-react-cra/.vscodeignore)
|
||||
webview-ui/src/**
|
||||
|
||||
+19
-3
@@ -4,11 +4,27 @@ All notable changes to the "claude-dev" extension will be documented in this fil
|
||||
|
||||
<!-- Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. -->
|
||||
|
||||
## [Unreleased]
|
||||
## [1.0.7]
|
||||
|
||||
## [0.0.7]
|
||||
- Replace `list_files` and `analyze_project` with more explicit `list_files_top_level`, `list_files_recursive`, and `view_source_code_definitions_top_level` to get source code definitions only for files relevant to the task
|
||||
|
||||
-
|
||||
## [1.0.6]
|
||||
|
||||
- Interact with CLI commands by sending messages to stdin and terminating long-running processes like servers
|
||||
- Export tasks to markdown files (useful as context for future tasks)
|
||||
|
||||
## [1.0.5]
|
||||
|
||||
- Claude now has context about vscode's visible editors and opened tabs
|
||||
|
||||
## [1.0.4]
|
||||
|
||||
- Open in the editor (using menu bar or `Claude Dev: Open In New Tab` in command palette) to see how Claude updates your workspace more clearly
|
||||
- New `analyze_project` tool to help Claude get a comprehensive overview of your project's source code definitions and file structure
|
||||
- Provide feedback to tool use like terminal commands and file edits
|
||||
- Updated max output tokens to 8192 so less lazy coding (`// rest of code here...`)
|
||||
- Added ability to retry failed API requests (helpful for rate limits)
|
||||
- Quality of life improvements like markdown rendering, memory optimizations, better theme support
|
||||
|
||||
## [0.0.6]
|
||||
|
||||
|
||||
@@ -6,23 +6,21 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="https://marketplace.visualstudio.com/items?itemName=saoudrizwan.claude-dev"><strong>Download VSCode Extension</strong></a>
|
||||
|
||||
<a href="https://www.youtube.com/@saoudrizwan"><strong>Tutorials (coming soon)</strong></a>
|
||||
|
||||
<a href="#contribution"><strong>Make Contribution</strong></a>
|
||||
</p>
|
||||
|
||||
Thanks to [Claude 3.5 Sonnet's agentic coding capabilities](https://www-cdn.anthropic.com/fed9cc193a14b84131812372d8d5857f8f304c52/Model_Card_Claude_3_Addendum.pdf) Claude Dev can handle complex software development tasks step-by-step. With tools that let him read & write files, analyze project source code, and execute terminal commands (after you grant permission), he can assist you in ways that go beyond simple code completion or tech support. From building software projects to running system operations, Claude Dev is only limited by your imagination.
|
||||
|
||||
While autonomous AI scripts traditionally run in sandboxed environments, Claude Dev offers a human-in-the-loop GUI to supervise every file change and command executed, providing a safe and accessible way to explore the potential of agentic AI.
|
||||
While autonomous AI scripts traditionally run in sandboxed environments, Claude Dev provides a human-in-the-loop GUI to supervise every file changed and command executed, providing a safe and accessible way to explore the potential of agentic AI.
|
||||
|
||||
- View syntax highlighted file previews and diffs for every change Claude makes
|
||||
- Streams command execution output into the chat, so you never have to open a terminal yourself
|
||||
- Runs CLI commands directly in chat, so you never have to open a terminal yourself (+ respond to interactive commands by sending a message)
|
||||
- Presents permission buttons (i.e. 'Approve CLI command') before tool use or sending information to the API
|
||||
- Keep track of total tokens and API usage cost for the entire task loop and individual requests
|
||||
- Set a maximum # of API requests allowed for a task before being prompted for permission to proceed
|
||||
- When a task is completed, Claude Dev determines if he can present the result to you with a CLI command like `open -a "Google Chrome" index.html`, which you run with a click of a button
|
||||
|
||||
_**Pro tip**: Use the `Cmd + Shift + P` shortcut to open the command palette and type `Claude Dev: Open In New Tab` to start a new task right in your editor._
|
||||
|
||||
## How it works
|
||||
|
||||
Claude Dev uses an agentic loop style implementation with chain-of-thought prompting and access to powerful tools that give him the ability to accomplish nearly any task. Start by providing a task and the agentic loop fires off, where it might use certain tools (with your permission) to accomplish each step in its thought process.
|
||||
@@ -31,13 +29,28 @@ Claude Dev uses an agentic loop style implementation with chain-of-thought promp
|
||||
|
||||
Claude Dev has access to the following capabilities:
|
||||
|
||||
1. **`execute_command`**: Execute CLI commands on the system
|
||||
2. **`analyze_project`**: Analyze the project's source code and file structure
|
||||
3. **`list_files`**: List all file paths at the top level of the specified directory
|
||||
4. **`read_file`**: Read the contents of a file at the specified path
|
||||
5. **`write_to_file`**: Write content to a file at the specified path
|
||||
6. **`ask_followup_question`**: Ask the user a question to gather additional information needed to complete a task
|
||||
7. **`attempt_completion`**: Present the result to the user after completing a task, potentially with a CLI command to kickoff a demonstration
|
||||
1. **`execute_command`**: Execute CLI commands on the system (only with your permission, output is streamed into the chat and you can respond to stdin or exit long-running processes when you're ready)
|
||||
2. **`list_files_top_level`**: List all paths for files at the top level of the specified directory (useful for generic file operations like retrieving a file from your Desktop)
|
||||
3. **`list_files_recursive`**: List all paths for files in the specified directory and nested subdirectories (excludes files in .gitignore)
|
||||
4. **`view_source_code_definitions_top_level`**: Parses all source code files at the top level of the specified directory to extract names of key elements like classes and functions (see more below)
|
||||
5. **`read_file`**: Read the contents of a file at the specified path
|
||||
6. **`write_to_file`**: Write content to a file at the specified path, automatically creating any necessary directories
|
||||
7. **`ask_followup_question`**: Ask the user a question to gather additional information needed to complete a task (due to the autonomous nature of the program, this isn't a typical chatbot–Claude Dev must explicitly interrupt his task loop to ask for more information)
|
||||
8. **`attempt_completion`**: Present the result to the user after completing a task, potentially with a CLI command to kickoff a demonstration
|
||||
|
||||
### Working in Existing Projects
|
||||
|
||||
Developers tend to name directories, files, classes, functions, and other components in ways that reflect their purpose and role within the larger system. These names often encapsulate high-level concepts and relationships that are crucial for understanding a project's overall architecture.
|
||||
|
||||
Claude Dev leverages the fact that large language models are fundamentally built on natural language processing, and by focusing on these named elements we provide the LLM with a structural understanding of the codebase that aligns closely with how developers conceptualize and organize their code. By effectively extracting the "language" of the codebase, we enable the LLM to grasp structure and intent without wasting context on implementation details.
|
||||
|
||||
1. **File Structure**: Claude first uses the `list_files_recursive` tool to get a complete picture of the project's file structure. It turns out Claude 3.5 Sonnet is _really_ good at inferring what it needs to process further just from these file names alone.
|
||||
|
||||
2. **High-Level Code Overview**: Claude may then use the `view_source_code_definitions_top_level` tool on specific directories of interest. This tool uses [tree-sitter](https://github.com/tree-sitter/tree-sitter) to parse source code with custom tag queries that extract names of classes, functions, methods, and other definitions. `view_source_code_definitions_top_level` works by first identifying source code files that tree-sitter can parse (currently supports `python`, `javascript`, `typescript`, `ruby`, `go`, `java`, `php`, `rust`, `c`, `c++`, `c#`, `swift`), then parsing each file into an abstract syntax tree, and finally applying a language-specific query to extract definition names (you can see the exact query used for each language in `src/parse-source-code/queries`). The results are formatted into a concise & readable output that Claude can easily interpret to quickly understand the code's structure and purpose.
|
||||
|
||||
3. **Read Relevant Files**: With the insights gained from the project file structure and high-level code overview, Claude can then use the `read_file` tool to examine specific files that are most relevant to the task at hand.
|
||||
|
||||
By carefully managing what information is added to context, Claude can provide valuable assistance even for complex, large-scale projects without overwhelming its context window (200k tokens, translating to roughly 150k words or about 500 pages of a typical book).
|
||||
|
||||
### Only With Your Permission
|
||||
|
||||
@@ -68,6 +81,7 @@ To build Claude Dev locally, follow these steps:
|
||||
|
||||
- ["Claude Sonnet 3.5 Artifacts in VSCode With This Extension"](https://www.youtube.com/watch?v=5FbZ8ALfSTs) by [CoderOne](https://www.youtube.com/@CoderOne)
|
||||
- ["Meet Claude Dev — An Open-Source AI Programmer In VS Code"](https://generativeai.pub/meet-claude-dev-an-open-source-autonomous-ai-programmer-in-vs-code-f457f9821b7b) by [Jim Clyde Monge](https://jimclydemonge.medium.com/)
|
||||
- ["AI Development with Claude Dev"](https://www.linkedin.com/pulse/ai-development-claude-dev-shannon-lal-3ql3e/) by Shannon Lal
|
||||
- ["Code Smarter with Claude Dev: An AI Programmer for Your Projects"](https://www.linkedin.com/pulse/code-smarter-claude-dev-ai-programmer-your-projects-iana-detochka-jiqpe) by Iana D.
|
||||
- [Claude Dev also hit top 10 posts of all time on r/ClaudeAI (thank you for all the lovely comments)](https://www.reddit.com/r/ClaudeAI/comments/1e3h0f1/my_submission_to_anthropics_build_with_claude/)
|
||||
|
||||
@@ -77,7 +91,7 @@ This project is licensed under the MIT License. See the [LICENSE](./LICENSE) fil
|
||||
|
||||
## Questions?
|
||||
|
||||
Contact me on twitter <a href="https://x.com/sdrzn" target="_blank">@sdrzn</a>. Please create an <a href="https://github.com/saoudrizwan/claude-dev/issues">issue</a> if you come across a bug or would like a feature to be added.
|
||||
Contact me on X <a href="https://x.com/sdrzn" target="_blank">@sdrzn</a>. Please create an <a href="https://github.com/saoudrizwan/claude-dev/issues">issue</a> if you come across a bug or would like a feature to be added.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d234220ea054c34483c74b662c34115f3ed7ce538ba135a59c599d6eab68105b
|
||||
size 83423820
|
||||
oid sha256:180820972a22a69f8064e13b431184599facdc2846766df1f125ef5bd19499f5
|
||||
size 20134174
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "claude-dev",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.73",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "claude-dev",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.73",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.24.3",
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "claude-dev",
|
||||
"displayName": "Claude Dev",
|
||||
"description": "Autonomous software engineer right in your IDE, capable of reading/writing files, executing commands, and more with your permission every step of the way.",
|
||||
"version": "1.0.4",
|
||||
"description": "Autonomous software engineer right in your IDE, capable of creating/editing files, executing commands, and more with your permission every step of the way.",
|
||||
"version": "1.0.73",
|
||||
"icon": "icon.png",
|
||||
"engines": {
|
||||
"vscode": "^1.84.0"
|
||||
|
||||
+199
-111
@@ -1,24 +1,24 @@
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import defaultShell from "default-shell"
|
||||
import * as diff from "diff"
|
||||
import { execa } from "execa"
|
||||
import { execa, ExecaError } from "execa"
|
||||
import fs from "fs/promises"
|
||||
import { globby } from "globby"
|
||||
import os from "os"
|
||||
import osName from "os-name"
|
||||
import pWaitFor from "p-wait-for"
|
||||
import * as path from "path"
|
||||
import { serializeError } from "serialize-error"
|
||||
import { DEFAULT_MAX_REQUESTS_PER_TASK } from "./shared/Constants"
|
||||
import { Tool, ToolName } from "./shared/Tool"
|
||||
import { ClaudeAsk, ClaudeSay, ClaudeSayTool } from "./shared/ExtensionMessage"
|
||||
import * as vscode from "vscode"
|
||||
import pWaitFor from "p-wait-for"
|
||||
import { ClaudeAskResponse } from "./shared/WebviewMessage"
|
||||
import { listFiles, parseSourceCodeForDefinitionsTopLevel } from "./parse-source-code"
|
||||
import { ClaudeDevProvider } from "./providers/ClaudeDevProvider"
|
||||
import { ClaudeRequestResult } from "./shared/ClaudeRequestResult"
|
||||
import os from "os"
|
||||
import { analyzeProject } from "./analyze-project"
|
||||
import { DEFAULT_MAX_REQUESTS_PER_TASK } from "./shared/Constants"
|
||||
import { ClaudeAsk, ClaudeSay, ClaudeSayTool } from "./shared/ExtensionMessage"
|
||||
import { Tool, ToolName } from "./shared/Tool"
|
||||
import { ClaudeAskResponse } from "./shared/WebviewMessage"
|
||||
|
||||
const SYSTEM_PROMPT = `You are Claude Dev, a highly skilled software developer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.
|
||||
const SYSTEM_PROMPT =
|
||||
() => `You are Claude Dev, a highly skilled software developer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices.
|
||||
|
||||
====
|
||||
|
||||
@@ -26,24 +26,24 @@ CAPABILITIES
|
||||
|
||||
- You can read and analyze code in various programming languages, and can write clean, efficient, and well-documented code.
|
||||
- You can debug complex issues and providing detailed explanations, offering architectural insights and design patterns.
|
||||
- You have access to tools that let you analyze software projects, execute CLI commands on the user's computer, list files in a directory, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- For example, when asked to make edits or improvements you might use the analyze_project and read_file tools to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the write_to_file tool to implement changes.
|
||||
- You can use the analyze_project tool to get a comprehensive view of a software project's file structure and important syntactic nodes such as functions, classes, and methods. This can be particularly useful when you need to understand the broader context and relationships between different parts of the code, as well as the overall organization of files and directories.
|
||||
- The execute_command tool lets you run commands on the user's computer and should be used whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run.
|
||||
- You have access to tools that let you execute CLI commands on the user's computer, list files in a directory (top level or recursively), extract source code definitions, read and write files, and ask follow-up questions. These tools help you effectively accomplish a wide range of tasks, such as writing code, making edits or improvements to existing files, understanding the current state of a project, performing system operations, and much more.
|
||||
- You can use the list_files_recursive tool to get an overview of the project's file structure, which can provide key insights into the project from directory/file names (how developers conceptualize and organize their code) or file extensions (the language used). The list_files_top_level tool is better suited for generic directories you don't necessarily need the nested structure of, like the Desktop.
|
||||
- You can use the view_source_code_definitions_top_level tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- For example, when asked to make edits or improvements you might use list_files_recursive to get an overview of the project's file structure, then view_source_code_definitions_top_level to get an overview of source code definitions for files located in relevant directories, then read_file to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the write_to_file tool to implement changes.
|
||||
- The execute_command tool lets you run commands on the user's computer and should be used whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the user has the ability to send input to stdin and terminate the command on their own if needed.
|
||||
|
||||
====
|
||||
|
||||
RULES
|
||||
|
||||
- Unless otherwise specified by the user, you MUST accomplish your task within the following directory: ${
|
||||
vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop")
|
||||
}
|
||||
vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ??
|
||||
path.join(os.homedir(), "Desktop")
|
||||
}
|
||||
- Your current working directory is '${process.cwd()}', and you cannot \`cd\` into a different directory to complete a task. You are stuck operating from '${process.cwd()}', so be sure to pass in the appropriate 'path' parameter when using tools that require a path.
|
||||
- If you do not know the contents of an existing file you need to edit, use the read_file tool to help you make informed changes. However if you have seen this file before, you should be able to remember its contents.
|
||||
- If you do not know the contents of an existing file you need to edit, use the read_file tool to help you make informed changes. However if you have seen this file before, you can assume its contents have not changed since you last read it or wrote to it.
|
||||
- When editing files, always provide the complete file content in your response, regardless of the extent of changes. The system handles diff generation automatically.
|
||||
- Before using the execute_command tool, you must first think about the System Information context provided by the user to understand their environment and tailor your commands to ensure they are compatible with the user's system.
|
||||
- When using the execute_command tool, avoid running servers or executing commands that don't terminate on their own (e.g. Flask web servers, continuous scripts). If a task requires such a process or server, explain in your task completion result why you can't execute it directly and provide clear instructions on how the user can run it themselves.
|
||||
- Try not to use the analyze_project tool more than once since you can refer back to it along with any changes you made to get an adequate understanding of the project. But don't be hesitant to use it in the first place when you know you will be doing a coding task on an existing project. Prefer to use analyze_project over list_files, unless you think list_files is more appropriate for the job i.e. when viewing files on the Desktop.
|
||||
- Before using the execute_command tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system.
|
||||
- When creating a new project (such as an app, website, or any software project), unless the user specifies otherwise, organize all new files within a dedicated project directory. Use appropriate file paths when writing files, as the write_to_file tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
|
||||
- You must try to use multiple tools in one request when possible. For example if you were to create a website, you would use the write_to_file tool to create the necessary files with their appropriate contents all at once. Or if you wanted to analyze a project, you could use the read_file tool multiple times to look at several key files. This will help you accomplish the user's task more efficiently.
|
||||
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
|
||||
@@ -64,7 +64,7 @@ You accomplish a given task iteratively, breaking it down into clear steps and w
|
||||
1. Analyze the user's task and set clear, achievable goals to accomplish it. Prioritize these goals in a logical order.
|
||||
2. Work through these goals sequentially, utilizing available tools as necessary. Each goal should correspond to a distinct step in your problem-solving process.
|
||||
3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. Before calling a tool, do some analysis within <thinking></thinking> tags. First, think about which of the provided tools is the relevant tool to answer the user's request. Second, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool call. BUT, if one of the values for a required parameter is missing, DO NOT invoke the function (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters using the ask_followup_question tool. DO NOT ask for more information on optional parameters if it is not provided.
|
||||
4. Once you've completed the user's task, you must use the attempt_completion tool to present the result of the task to the user. You may also provide a CLI command to showcase the result of your task; this can be particularly useful for web development tasks, where you can run e.g. \`open index.html\` to show the website you've built. Avoid commands that run indefinitely (like servers). Instead, if such a command is needed, include instructions for the user to run it in the 'result' parameter.
|
||||
4. Once you've completed the user's task, you must use the attempt_completion tool to present the result of the task to the user. You may also provide a CLI command to showcase the result of your task; this can be particularly useful for web development tasks, where you can run e.g. \`open index.html\` to show the website you've built.
|
||||
5. The user may provide feedback, which you can use to make improvements and try again. But DO NOT continue in pointless back and forth conversations, i.e. don't end your responses with questions or offers for further assistance.
|
||||
|
||||
====
|
||||
@@ -73,45 +73,42 @@ SYSTEM INFORMATION
|
||||
|
||||
Operating System: ${osName()}
|
||||
Default Shell: ${defaultShell}
|
||||
VSCode Visible Files: ${
|
||||
vscode.window.visibleTextEditors
|
||||
?.map((editor) => editor.document?.uri?.fsPath)
|
||||
.filter(Boolean)
|
||||
.join(", ") || "(No files open)"
|
||||
}
|
||||
VSCode Opened Tabs: ${
|
||||
vscode.window.tabGroups.all
|
||||
.flatMap((group) => group.tabs)
|
||||
.map((tab) => (tab.input as vscode.TabInputText)?.uri?.fsPath)
|
||||
.filter(Boolean)
|
||||
.join(", ") || "(No tabs open)"
|
||||
}
|
||||
`
|
||||
|
||||
const tools: Tool[] = [
|
||||
{
|
||||
name: "execute_command",
|
||||
description:
|
||||
"Execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. Do not run servers or commands that don't terminate on their own. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run.",
|
||||
"Execute a CLI command on the system. Use this when you need to perform system operations or run specific commands to accomplish any step in the user's task. You must tailor your command to the user's system and provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, as they are more flexible and easier to run.",
|
||||
input_schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
command: {
|
||||
type: "string",
|
||||
description:
|
||||
"The CLI command to execute. This should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions. Avoid commands that run indefinitely (like servers) that don't terminate on their own.",
|
||||
"The CLI command to execute. This should be valid for the current operating system. Ensure the command is properly formatted and does not contain any harmful instructions.",
|
||||
},
|
||||
},
|
||||
required: ["command"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "analyze_project",
|
||||
name: "list_files_top_level",
|
||||
description:
|
||||
"Analyze the project structure by listing file paths and parsing supported source code to extract their key elements. This tool provides insights into the codebase structure, focusing on important code constructs like functions, classes, and methods. This also helps to understand the contents and structure of a directory by examining file names and extensions. All this information can guide decision-making on which files to process or explore further.",
|
||||
input_schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
path: {
|
||||
type: "string",
|
||||
description:
|
||||
"The path of the directory to analyze. The tool will recursively scan this directory, list all file paths, and parse supported source code files.",
|
||||
},
|
||||
},
|
||||
required: ["path"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list_files",
|
||||
description:
|
||||
"List all files and directories at the top level of the specified directory. This should only be used for generic directories you don't necessarily need the nested structure of, like the Desktop. If you think you need the nested structure of a directory, use the analyze_project tool instead.",
|
||||
"List all files and directories at the top level of the specified directory. This should only be used for generic directories you don't necessarily need the nested structure of, like the Desktop.",
|
||||
input_schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -123,6 +120,37 @@ const tools: Tool[] = [
|
||||
required: ["path"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list_files_recursive",
|
||||
description:
|
||||
"Recursively list all files and directories within the specified directory. This provides a comprehensive view of the project structure, and can guide decision-making on which files to process or explore further.",
|
||||
input_schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
path: {
|
||||
type: "string",
|
||||
description: "The path of the directory to recursively list contents for.",
|
||||
},
|
||||
},
|
||||
required: ["path"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "view_source_code_definitions_top_level",
|
||||
description:
|
||||
"Parse all source code files at the top level of the specified directory to extract names of key elements like classes and functions. This tool provides insights into the codebase structure and important constructs, encapsulating high-level concepts and relationships that are crucial for understanding the overall architecture.",
|
||||
input_schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
path: {
|
||||
type: "string",
|
||||
description:
|
||||
"The path of the directory to parse top level source code files for to view their definitions.",
|
||||
},
|
||||
},
|
||||
required: ["path"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "read_file",
|
||||
description:
|
||||
@@ -202,6 +230,7 @@ export class ClaudeDev {
|
||||
private requestCount = 0
|
||||
private askResponse?: ClaudeAskResponse
|
||||
private askResponseText?: string
|
||||
private lastMessageTs?: number
|
||||
private providerRef: WeakRef<ClaudeDevProvider>
|
||||
abort: boolean = false
|
||||
|
||||
@@ -233,9 +262,14 @@ export class ClaudeDev {
|
||||
}
|
||||
this.askResponse = undefined
|
||||
this.askResponseText = undefined
|
||||
await this.providerRef.deref()?.addClaudeMessage({ ts: Date.now(), type: "ask", ask: type, text: question })
|
||||
const askTs = Date.now()
|
||||
this.lastMessageTs = askTs
|
||||
await this.providerRef.deref()?.addClaudeMessage({ ts: askTs, type: "ask", ask: type, text: question })
|
||||
await this.providerRef.deref()?.postStateToWebview()
|
||||
await pWaitFor(() => this.askResponse !== undefined, { interval: 100 })
|
||||
await pWaitFor(() => this.askResponse !== undefined || this.lastMessageTs !== askTs, { interval: 100 })
|
||||
if (this.lastMessageTs !== askTs) {
|
||||
throw new Error("Current ask promise was ignored") // could happen if we send multiple asks in a row i.e. with command_output. It's important that when we know an ask could fail, it is handled gracefully
|
||||
}
|
||||
const result = { response: this.askResponse!, text: this.askResponseText }
|
||||
this.askResponse = undefined
|
||||
this.askResponseText = undefined
|
||||
@@ -246,7 +280,9 @@ export class ClaudeDev {
|
||||
if (this.abort) {
|
||||
throw new Error("ClaudeDev instance aborted")
|
||||
}
|
||||
await this.providerRef.deref()?.addClaudeMessage({ ts: Date.now(), type: "say", say: type, text: text })
|
||||
const sayTs = Date.now()
|
||||
this.lastMessageTs = sayTs
|
||||
await this.providerRef.deref()?.addClaudeMessage({ ts: sayTs, type: "say", say: type, text: text })
|
||||
await this.providerRef.deref()?.postStateToWebview()
|
||||
}
|
||||
|
||||
@@ -298,10 +334,12 @@ export class ClaudeDev {
|
||||
return this.writeToFile(toolInput.path, toolInput.content)
|
||||
case "read_file":
|
||||
return this.readFile(toolInput.path)
|
||||
case "analyze_project":
|
||||
return this.analyzeProject(toolInput.path)
|
||||
case "list_files":
|
||||
return this.listFiles(toolInput.path)
|
||||
case "list_files_top_level":
|
||||
return this.listFilesTopLevel(toolInput.path)
|
||||
case "list_files_recursive":
|
||||
return this.listFilesRecursive(toolInput.path)
|
||||
case "view_source_code_definitions_top_level":
|
||||
return this.viewSourceCodeDefinitionsTopLevel(toolInput.path)
|
||||
case "execute_command":
|
||||
return this.executeCommand(toolInput.command)
|
||||
case "ask_followup_question":
|
||||
@@ -345,6 +383,7 @@ export class ClaudeDev {
|
||||
})
|
||||
.join("")
|
||||
|
||||
vscode.window.showTextDocument(vscode.Uri.file(filePath), { preview: false })
|
||||
const { response, text } = await this.ask(
|
||||
"tool",
|
||||
JSON.stringify({
|
||||
@@ -377,6 +416,7 @@ export class ClaudeDev {
|
||||
}
|
||||
await fs.mkdir(path.dirname(filePath), { recursive: true })
|
||||
await fs.writeFile(filePath, newContent)
|
||||
vscode.window.showTextDocument(vscode.Uri.file(filePath), { preview: false })
|
||||
return `New file created and content written to ${filePath}`
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -408,64 +448,26 @@ export class ClaudeDev {
|
||||
}
|
||||
}
|
||||
|
||||
async analyzeProject(dirPath: string): Promise<string> {
|
||||
async listFilesTopLevel(dirPath: string): Promise<string> {
|
||||
try {
|
||||
const analysis = await analyzeProject(dirPath)
|
||||
const files = await listFiles(dirPath, false)
|
||||
const result = files
|
||||
.map((file) => {
|
||||
const relativePath = path.relative(dirPath, file)
|
||||
return file.endsWith("/") ? relativePath + "/" : relativePath
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const aIsDir = a.endsWith("/")
|
||||
const bIsDir = b.endsWith("/")
|
||||
if (aIsDir !== bIsDir) {
|
||||
return aIsDir ? -1 : 1
|
||||
}
|
||||
return a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" })
|
||||
})
|
||||
.join("\n")
|
||||
const { response, text } = await this.ask(
|
||||
"tool",
|
||||
JSON.stringify({ tool: "analyzeProject", path: dirPath, content: analysis } as ClaudeSayTool)
|
||||
)
|
||||
if (response !== "yesButtonTapped") {
|
||||
if (response === "textResponse" && text) {
|
||||
await this.say("user_feedback", text)
|
||||
return `The user denied this operation and provided the following feedback:\n\"${text}\"`
|
||||
}
|
||||
return "The user denied this operation."
|
||||
}
|
||||
return analysis
|
||||
} catch (error) {
|
||||
const errorString = `Error analyzing project: ${JSON.stringify(serializeError(error))}`
|
||||
this.say(
|
||||
"error",
|
||||
`Error analyzing project:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`
|
||||
)
|
||||
return errorString
|
||||
}
|
||||
}
|
||||
|
||||
async listFiles(dirPath: string): Promise<string> {
|
||||
const absolutePath = path.resolve(dirPath)
|
||||
const root = process.platform === "win32" ? path.parse(absolutePath).root : "/"
|
||||
const isRoot = absolutePath === root
|
||||
if (isRoot) {
|
||||
const { response, text } = await this.ask(
|
||||
"tool",
|
||||
JSON.stringify({ tool: "listFiles", path: dirPath, content: root } as ClaudeSayTool)
|
||||
)
|
||||
if (response !== "yesButtonTapped") {
|
||||
if (response === "textResponse" && text) {
|
||||
await this.say("user_feedback", text)
|
||||
return `The user denied this operation and provided the following feedback:\n\"${text}\"`
|
||||
}
|
||||
return "The user denied this operation."
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
try {
|
||||
const options = {
|
||||
cwd: dirPath,
|
||||
dot: true, // Allow patterns to match files/directories that start with '.', even if the pattern does not start with '.'
|
||||
absolute: false,
|
||||
markDirectories: true, // Append a / on any directories matched
|
||||
onlyFiles: false,
|
||||
}
|
||||
// * globs all files in one dir, ** globs files in nested directories
|
||||
const entries = await globby("*", options)
|
||||
const result = entries.join("\n")
|
||||
const { response, text } = await this.ask(
|
||||
"tool",
|
||||
JSON.stringify({ tool: "listFiles", path: dirPath, content: result } as ClaudeSayTool)
|
||||
JSON.stringify({ tool: "listFilesTopLevel", path: dirPath, content: result } as ClaudeSayTool)
|
||||
)
|
||||
if (response !== "yesButtonTapped") {
|
||||
if (response === "textResponse" && text) {
|
||||
@@ -487,6 +489,63 @@ export class ClaudeDev {
|
||||
}
|
||||
}
|
||||
|
||||
async listFilesRecursive(dirPath: string): Promise<string> {
|
||||
try {
|
||||
const files = await listFiles(dirPath, true)
|
||||
const result = files.map((file) => path.relative(dirPath, file)).join("\n")
|
||||
const { response, text } = await this.ask(
|
||||
"tool",
|
||||
JSON.stringify({ tool: "listFilesRecursive", path: dirPath, content: result } as ClaudeSayTool)
|
||||
)
|
||||
if (response !== "yesButtonTapped") {
|
||||
if (response === "textResponse" && text) {
|
||||
await this.say("user_feedback", text)
|
||||
return `The user denied this operation and provided the following feedback:\n\"${text}\"`
|
||||
}
|
||||
return "The user denied this operation."
|
||||
}
|
||||
return result
|
||||
} catch (error) {
|
||||
const errorString = `Error listing files recursively: ${JSON.stringify(serializeError(error))}`
|
||||
this.say(
|
||||
"error",
|
||||
`Error listing files recursively:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`
|
||||
)
|
||||
return errorString
|
||||
}
|
||||
}
|
||||
|
||||
async viewSourceCodeDefinitionsTopLevel(dirPath: string): Promise<string> {
|
||||
try {
|
||||
const result = await parseSourceCodeForDefinitionsTopLevel(dirPath)
|
||||
const { response, text } = await this.ask(
|
||||
"tool",
|
||||
JSON.stringify({
|
||||
tool: "viewSourceCodeDefinitionsTopLevel",
|
||||
path: dirPath,
|
||||
content: result,
|
||||
} as ClaudeSayTool)
|
||||
)
|
||||
if (response !== "yesButtonTapped") {
|
||||
if (response === "textResponse" && text) {
|
||||
await this.say("user_feedback", text)
|
||||
return `The user denied this operation and provided the following feedback:\n\"${text}\"`
|
||||
}
|
||||
return "The user denied this operation."
|
||||
}
|
||||
return result
|
||||
} catch (error) {
|
||||
const errorString = `Error parsing source code definitions: ${JSON.stringify(serializeError(error))}`
|
||||
this.say(
|
||||
"error",
|
||||
`Error parsing source code definitions:\n${
|
||||
error.message ?? JSON.stringify(serializeError(error), null, 2)
|
||||
}`
|
||||
)
|
||||
return errorString
|
||||
}
|
||||
}
|
||||
|
||||
async executeCommand(command: string, returnEmptyStringOnSuccess: boolean = false): Promise<string> {
|
||||
const { response, text } = await this.ask("command", command)
|
||||
if (response !== "yesButtonTapped") {
|
||||
@@ -498,18 +557,47 @@ export class ClaudeDev {
|
||||
}
|
||||
try {
|
||||
let result = ""
|
||||
// execa by default tries to convery bash into javascript
|
||||
// by using shell: true we use sh on unix or cmd.exe on windows
|
||||
// also worth noting that execa`input` runs commands and the execa() creates a new instance
|
||||
for await (const line of execa({ shell: true })`${command}`) {
|
||||
this.say("command_output", line) // stream output to user in realtime
|
||||
result += `${line}\n`
|
||||
// execa by default tries to convert bash into javascript, so need to specify `shell: true` to use sh on unix or cmd.exe on windows
|
||||
// also worth noting that execa`input` and the execa(command) have nuanced differences like the template literal version handles escaping for you, while with the function call, you need to be more careful about how arguments are passed, especially when using shell: true.
|
||||
// execa returns a promise-like object that is both a promise and a Subprocess that has properties like stdin
|
||||
const subprocess = execa({ shell: true })`${command}`
|
||||
|
||||
try {
|
||||
for await (const chunk of subprocess) {
|
||||
const line = chunk.toString()
|
||||
// stream output to user in realtime
|
||||
// do not await as we are not waiting for a response
|
||||
this.ask("command_output", line)
|
||||
.then(({ response, text }) => {
|
||||
// if this ask promise is not ignored, that means the user responded to it somehow either by clicking primary button or by typing text
|
||||
if (response === "yesButtonTapped") {
|
||||
// SIGINT is typically what's sent when a user interrupts a process (like pressing Ctrl+C)
|
||||
subprocess.kill("SIGINT") // will result in for loop throwing error
|
||||
} else {
|
||||
// if the user sent some input, we send it to the command stdin
|
||||
// add newline as cli programs expect a newline after each input
|
||||
subprocess.stdin?.write(text + "\n")
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// this can only happen if this ask promise was ignored, so ignore this error
|
||||
})
|
||||
result += `${line}\n`
|
||||
}
|
||||
} catch (e) {
|
||||
if ((e as ExecaError).signal === "SIGINT") {
|
||||
const line = `\nUser exited command...`
|
||||
await this.say("command_output", line)
|
||||
result += line
|
||||
} else {
|
||||
throw e // if the command was not terminated by user, let outer catch handle it as a real error
|
||||
}
|
||||
}
|
||||
// for attemptCompletion, we don't want to return the command output
|
||||
if (returnEmptyStringOnSuccess) {
|
||||
return ""
|
||||
}
|
||||
return `Command executed successfully. Output:\n${result}`
|
||||
return `Command Output:\n${result}`
|
||||
} catch (e) {
|
||||
const error = e as any
|
||||
let errorMessage = error.message || JSON.stringify(serializeError(error), null, 2)
|
||||
@@ -552,7 +640,7 @@ export class ClaudeDev {
|
||||
model: "claude-3-5-sonnet-20240620", // https://docs.anthropic.com/en/docs/about-claude/models
|
||||
// beta max tokens
|
||||
max_tokens: 8192,
|
||||
system: SYSTEM_PROMPT,
|
||||
system: SYSTEM_PROMPT(),
|
||||
messages: (await this.providerRef.deref()?.getApiConversationHistory()) || [],
|
||||
tools: tools,
|
||||
tool_choice: { type: "auto" },
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
- struct definitions
|
||||
- enum definitions
|
||||
- union definitions
|
||||
- type aliases
|
||||
- method definitions
|
||||
- function definitions
|
||||
- trait definitions
|
||||
- module definitions
|
||||
- macro definitions
|
||||
*/
|
||||
export default `
|
||||
(struct_item
|
||||
name: (type_identifier) @name.definition.class) @definition.class
|
||||
|
||||
(enum_item
|
||||
name: (type_identifier) @name.definition.class) @definition.class
|
||||
|
||||
(union_item
|
||||
name: (type_identifier) @name.definition.class) @definition.class
|
||||
|
||||
(type_item
|
||||
name: (type_identifier) @name.definition.class) @definition.class
|
||||
|
||||
(declaration_list
|
||||
(function_item
|
||||
name: (identifier) @name.definition.method)) @definition.method
|
||||
|
||||
(function_item
|
||||
name: (identifier) @name.definition.function) @definition.function
|
||||
|
||||
(trait_item
|
||||
name: (type_identifier) @name.definition.interface) @definition.interface
|
||||
|
||||
(mod_item
|
||||
name: (identifier) @name.definition.module) @definition.module
|
||||
|
||||
(macro_definition
|
||||
name: (identifier) @name.definition.macro) @definition.macro
|
||||
`
|
||||
+2
-2
@@ -39,7 +39,7 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
const sidebarProvider = new ClaudeDevProvider(context, outputChannel)
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerWebviewViewProvider(ClaudeDevProvider.viewType, sidebarProvider, {
|
||||
vscode.window.registerWebviewViewProvider(ClaudeDevProvider.sideBarId, sidebarProvider, {
|
||||
webviewOptions: { retainContextWhenHidden: true },
|
||||
})
|
||||
)
|
||||
@@ -61,7 +61,7 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
//const column = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.viewColumn : undefined
|
||||
const lastCol = Math.max(...vscode.window.visibleTextEditors.map((editor) => editor.viewColumn || 0))
|
||||
const targetCol = Math.max(lastCol + 1, 1)
|
||||
const panel = vscode.window.createWebviewPanel(ClaudeDevProvider.viewType, "Claude Dev", targetCol, {
|
||||
const panel = vscode.window.createWebviewPanel(ClaudeDevProvider.tabPanelId, "Claude Dev", targetCol, {
|
||||
enableScripts: true,
|
||||
retainContextWhenHidden: true,
|
||||
localResourceRoots: [context.extensionUri],
|
||||
|
||||
@@ -3,45 +3,54 @@ import { globby } from "globby"
|
||||
import * as path from "path"
|
||||
import { LanguageParser, loadRequiredLanguageParsers } from "./languageParser"
|
||||
|
||||
async function analyzeProject(dirPath: string): Promise<string> {
|
||||
let result = ""
|
||||
// TODO: implement caching behavior to avoid having to keep analyzing project for new tasks.
|
||||
export async function parseSourceCodeForDefinitionsTopLevel(dirPath: string): Promise<string> {
|
||||
// Get all files at top level (not gitignored)
|
||||
const allFiles = await listFiles(dirPath, false)
|
||||
|
||||
// Get all files (not gitignored)
|
||||
const allFiles = await getAllProjectFiles(dirPath)
|
||||
let result = ""
|
||||
|
||||
// Separate files to parse and remaining files
|
||||
const { filesToParse, remainingFiles } = separateFiles(allFiles)
|
||||
|
||||
// Load only the necessary language parsers
|
||||
const languageParsers = await loadRequiredLanguageParsers(filesToParse)
|
||||
|
||||
// Parse specific files we have language parsers for
|
||||
const filesWithoutDefinitions: string[] = []
|
||||
// const filesWithoutDefinitions: string[] = []
|
||||
for (const file of filesToParse) {
|
||||
const definitions = await parseFile(file, languageParsers)
|
||||
if (definitions) {
|
||||
if (!result) {
|
||||
result += "# Source code definitions:\n\n"
|
||||
}
|
||||
result += `${path.relative(dirPath, file)}\n${definitions}\n`
|
||||
} else {
|
||||
filesWithoutDefinitions.push(file)
|
||||
}
|
||||
// else {
|
||||
// filesWithoutDefinitions.push(file)
|
||||
// }
|
||||
}
|
||||
|
||||
// List remaining files' paths
|
||||
result += "# Unparsed files:\n\n"
|
||||
filesWithoutDefinitions
|
||||
.concat(remainingFiles)
|
||||
.sort()
|
||||
.forEach((file) => {
|
||||
result += `${path.relative(dirPath, file)}\n`
|
||||
})
|
||||
// let didFindUnparsedFiles = false
|
||||
// filesWithoutDefinitions
|
||||
// .concat(remainingFiles)
|
||||
// .sort()
|
||||
// .forEach((file) => {
|
||||
// if (!didFindUnparsedFiles) {
|
||||
// result += "# Unparsed Files\n\n"
|
||||
// didFindUnparsedFiles = true
|
||||
// }
|
||||
// result += `${path.relative(dirPath, file)}\n`
|
||||
// })
|
||||
|
||||
return result
|
||||
return result ? result : "No source code definitions found."
|
||||
}
|
||||
|
||||
async function getAllProjectFiles(dirPath: string): Promise<string[]> {
|
||||
export async function listFiles(dirPath: string, recursive: boolean): Promise<string[]> {
|
||||
const absolutePath = path.resolve(dirPath)
|
||||
const root = process.platform === "win32" ? path.parse(absolutePath).root : "/"
|
||||
const isRoot = absolutePath === root
|
||||
if (isRoot) {
|
||||
return [root]
|
||||
}
|
||||
|
||||
const dirsToIgnore = [
|
||||
"node_modules",
|
||||
"__pycache__",
|
||||
@@ -65,12 +74,13 @@ async function getAllProjectFiles(dirPath: string): Promise<string[]> {
|
||||
cwd: dirPath,
|
||||
dot: true, // do not ignore hidden files/directories
|
||||
absolute: true,
|
||||
markDirectories: true,
|
||||
gitignore: true, // globby ignores any files that are gitignored
|
||||
ignore: dirsToIgnore, // just in case there is no gitignore, we ignore sensible defaults
|
||||
markDirectories: true, // Append a / on any directories matched
|
||||
gitignore: recursive, // globby ignores any files that are gitignored
|
||||
ignore: recursive ? dirsToIgnore : undefined, // just in case there is no gitignore, we ignore sensible defaults
|
||||
onlyFiles: recursive, // true by default, false means it will list directories on their own too
|
||||
}
|
||||
// * globs all files in one dir, ** globs files in nested directories
|
||||
const files = await globby("**", options)
|
||||
const files = await globby(recursive ? "**" : "*", options)
|
||||
return files
|
||||
}
|
||||
|
||||
@@ -98,8 +108,8 @@ function separateFiles(allFiles: string[]): { filesToParse: string[]; remainingF
|
||||
"php",
|
||||
"swift",
|
||||
].map((e) => `.${e}`)
|
||||
const filesToParse = allFiles.filter((file) => extensions.includes(path.extname(file)))
|
||||
const remainingFiles = allFiles.filter((file) => !extensions.includes(path.extname(file)))
|
||||
const filesToParse = allFiles.filter((file) => extensions.includes(path.extname(file))).slice(0, 50) // 50 files max
|
||||
const remainingFiles = allFiles.filter((file) => !filesToParse.includes(file))
|
||||
return { filesToParse, remainingFiles }
|
||||
}
|
||||
|
||||
@@ -182,5 +192,3 @@ async function parseFile(filePath: string, languageParsers: LanguageParser): Pro
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export { analyzeProject }
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
javaQuery,
|
||||
phpQuery,
|
||||
swiftQuery,
|
||||
} from "./tree-sitter-queries/tags"
|
||||
} from "./queries"
|
||||
|
||||
export interface LanguageParser {
|
||||
[key: string]: {
|
||||
@@ -3,7 +3,6 @@
|
||||
- union declarations
|
||||
- function declarations
|
||||
- typedef declarations
|
||||
- enum declarations
|
||||
*/
|
||||
export default `
|
||||
(struct_specifier name: (type_identifier) @name.definition.class body:(_)) @definition.class
|
||||
@@ -13,6 +12,4 @@ export default `
|
||||
(function_declarator declarator: (identifier) @name.definition.function) @definition.function
|
||||
|
||||
(type_definition declarator: (type_identifier) @name.definition.type) @definition.type
|
||||
|
||||
(enum_specifier name: (type_identifier) @name.definition.type) @definition.type
|
||||
`
|
||||
-3
@@ -4,7 +4,6 @@
|
||||
- function declarations
|
||||
- method declarations (with namespace scope)
|
||||
- typedef declarations
|
||||
- enum declarations
|
||||
- class declarations
|
||||
*/
|
||||
export default `
|
||||
@@ -20,7 +19,5 @@ export default `
|
||||
|
||||
(type_definition declarator: (type_identifier) @name.definition.type) @definition.type
|
||||
|
||||
(enum_specifier name: (type_identifier) @name.definition.type) @definition.type
|
||||
|
||||
(class_specifier name: (type_identifier) @name.definition.class) @definition.class
|
||||
`
|
||||
-3
@@ -2,7 +2,6 @@
|
||||
- function declarations (with associated comments)
|
||||
- method declarations (with associated comments)
|
||||
- type specifications
|
||||
- type references
|
||||
*/
|
||||
export default `
|
||||
(
|
||||
@@ -25,6 +24,4 @@ export default `
|
||||
|
||||
(type_spec
|
||||
name: (type_identifier) @name.definition.type) @definition.type
|
||||
|
||||
(type_identifier) @name.reference.type @reference.type
|
||||
`
|
||||
-3
@@ -2,7 +2,6 @@
|
||||
- class declarations
|
||||
- method declarations
|
||||
- interface declarations
|
||||
- superclass references
|
||||
*/
|
||||
export default `
|
||||
(class_declaration
|
||||
@@ -13,6 +12,4 @@ export default `
|
||||
|
||||
(interface_declaration
|
||||
name: (identifier) @name.definition.interface) @definition.interface
|
||||
|
||||
(superclass (type_identifier) @name.reference.class) @reference.class
|
||||
`
|
||||
-12
@@ -3,7 +3,6 @@
|
||||
- method definitions
|
||||
- named function declarations
|
||||
- arrow functions and function expressions assigned to variables
|
||||
- exported constants
|
||||
*/
|
||||
export default `
|
||||
(
|
||||
@@ -63,15 +62,4 @@ export default `
|
||||
(#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$")
|
||||
(#select-adjacent! @doc @definition.function)
|
||||
)
|
||||
|
||||
(export_statement value: (assignment_expression left: (identifier) @name right: ([
|
||||
(number)
|
||||
(string)
|
||||
(identifier)
|
||||
(undefined)
|
||||
(null)
|
||||
(new_expression)
|
||||
(binary_expression)
|
||||
(call_expression)
|
||||
]))) @definition.constant
|
||||
`
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
- struct definitions
|
||||
- method definitions
|
||||
- function definitions
|
||||
*/
|
||||
export default `
|
||||
(struct_item
|
||||
name: (type_identifier) @name.definition.class) @definition.class
|
||||
|
||||
(declaration_list
|
||||
(function_item
|
||||
name: (identifier) @name.definition.method)) @definition.method
|
||||
|
||||
(function_item
|
||||
name: (identifier) @name.definition.function) @definition.function
|
||||
`
|
||||
-15
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
- class declarations
|
||||
- protocol declarations
|
||||
- method declarations (including initializers and deinitializers)
|
||||
- property declarations
|
||||
- function declarations
|
||||
@@ -27,20 +26,6 @@ export default `
|
||||
)
|
||||
) @definition.method
|
||||
|
||||
(protocol_declaration
|
||||
(protocol_body
|
||||
[
|
||||
(protocol_function_declaration
|
||||
name: (simple_identifier) @name
|
||||
)
|
||||
(subscript_declaration
|
||||
(parameter (simple_identifier) @name)
|
||||
)
|
||||
(init_declaration "init" @name)
|
||||
]
|
||||
)
|
||||
) @definition.method
|
||||
|
||||
(class_declaration
|
||||
(class_body
|
||||
[
|
||||
-12
@@ -4,9 +4,6 @@
|
||||
- abstract method signatures
|
||||
- class declarations (including abstract classes)
|
||||
- module declarations
|
||||
- interface declarations
|
||||
- type alias declarations
|
||||
- enum declarations
|
||||
*/
|
||||
export default `
|
||||
(function_signature
|
||||
@@ -24,9 +21,6 @@ export default `
|
||||
(module
|
||||
name: (identifier) @name.definition.module) @definition.module
|
||||
|
||||
(interface_declaration
|
||||
name: (type_identifier) @name.definition.interface) @definition.interface
|
||||
|
||||
(function_declaration
|
||||
name: (identifier) @name.definition.function) @definition.function
|
||||
|
||||
@@ -35,10 +29,4 @@ export default `
|
||||
|
||||
(class_declaration
|
||||
name: (type_identifier) @name.definition.class) @definition.class
|
||||
|
||||
(type_alias_declaration
|
||||
name: (type_identifier) @name.definition.type) @definition.type
|
||||
|
||||
(enum_declaration
|
||||
name: (identifier) @name.definition.enum) @definition.enum
|
||||
`
|
||||
@@ -5,6 +5,8 @@ import { ClaudeDev } from "../ClaudeDev"
|
||||
import { ClaudeMessage, ExtensionMessage } from "../shared/ExtensionMessage"
|
||||
import { WebviewMessage } from "../shared/WebviewMessage"
|
||||
import { Anthropic } from "@anthropic-ai/sdk"
|
||||
import * as path from "path"
|
||||
import os from "os"
|
||||
|
||||
/*
|
||||
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
|
||||
@@ -13,12 +15,13 @@ https://github.com/KumarVariable/vscode-extension-sidebar-html/blob/master/src/c
|
||||
*/
|
||||
|
||||
export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
public static readonly viewType = "claude-dev.SidebarProvider"
|
||||
public static readonly sideBarId = "claude-dev.SidebarProvider" // used in package.json as the view's id. This value cannot be changed due to how vscode caches views based on their id, and updating the id would break existing instances of the extension.
|
||||
public static readonly tabPanelId = "claude-dev.TabPanelProvider"
|
||||
private disposables: vscode.Disposable[] = []
|
||||
private view?: vscode.WebviewView | vscode.WebviewPanel
|
||||
private providerInstanceIdentifier = Date.now()
|
||||
private claudeDev?: ClaudeDev
|
||||
private latestAnnouncementId = "jul-25-2024" // update to some unique identifier when we add a new announcement
|
||||
private latestAnnouncementId = "jul-29-2024" // update to some unique identifier when we add a new announcement
|
||||
|
||||
constructor(
|
||||
private readonly context: vscode.ExtensionContext,
|
||||
@@ -278,6 +281,9 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
await this.updateGlobalState("lastShownAnnouncementId", this.latestAnnouncementId)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
case "downloadTask":
|
||||
this.downloadTask()
|
||||
break
|
||||
// Add more switch case statements here as more webview message commands
|
||||
// are created within the webview context (i.e. inside media/main.js)
|
||||
}
|
||||
@@ -287,6 +293,82 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
|
||||
)
|
||||
}
|
||||
|
||||
async downloadTask() {
|
||||
// File name
|
||||
const date = new Date()
|
||||
const month = date.toLocaleString("en-US", { month: "short" }).toLowerCase()
|
||||
const day = date.getDate()
|
||||
const year = date.getFullYear()
|
||||
let hours = date.getHours()
|
||||
const minutes = date.getMinutes().toString().padStart(2, "0")
|
||||
const ampm = hours >= 12 ? "pm" : "am"
|
||||
hours = hours % 12
|
||||
hours = hours ? hours : 12 // the hour '0' should be '12'
|
||||
const fileName = `claude_dev_task_${month}-${day}-${year}_${hours}-${minutes}-${ampm}.md`
|
||||
|
||||
// Generate markdown
|
||||
const conversationHistory = await this.getApiConversationHistory()
|
||||
const markdownContent = conversationHistory
|
||||
.map((message) => {
|
||||
const role = message.role === "user" ? "**User:**" : "**Assistant:**"
|
||||
const content = Array.isArray(message.content)
|
||||
? message.content.map(this.formatContentBlockToMarkdown).join("\n")
|
||||
: message.content
|
||||
|
||||
return `${role}\n\n${content}\n\n`
|
||||
})
|
||||
.join("---\n\n")
|
||||
|
||||
// Prompt user for save location
|
||||
const saveUri = await vscode.window.showSaveDialog({
|
||||
filters: { Markdown: ["md"] },
|
||||
defaultUri: vscode.Uri.file(path.join(os.homedir(), "Downloads", fileName)),
|
||||
})
|
||||
|
||||
if (saveUri) {
|
||||
// Write content to the selected location
|
||||
await vscode.workspace.fs.writeFile(saveUri, Buffer.from(markdownContent))
|
||||
vscode.window.showTextDocument(saveUri, { preview: true })
|
||||
}
|
||||
}
|
||||
|
||||
private formatContentBlockToMarkdown(
|
||||
block:
|
||||
| Anthropic.TextBlockParam
|
||||
| Anthropic.ImageBlockParam
|
||||
| Anthropic.ToolUseBlockParam
|
||||
| Anthropic.ToolResultBlockParam
|
||||
): string {
|
||||
switch (block.type) {
|
||||
case "text":
|
||||
return block.text
|
||||
case "image":
|
||||
return `[Image: ${block.source.media_type}]`
|
||||
case "tool_use":
|
||||
let input: string
|
||||
if (typeof block.input === "object" && block.input !== null) {
|
||||
input = Object.entries(block.input)
|
||||
.map(([key, value]) => `${key.charAt(0).toUpperCase() + key.slice(1)}: ${value}`)
|
||||
.join("\n")
|
||||
} else {
|
||||
input = String(block.input)
|
||||
}
|
||||
return `[Tool Use: ${block.name}]\n${input}`
|
||||
case "tool_result":
|
||||
if (typeof block.content === "string") {
|
||||
return `[Tool Result${block.is_error ? " (Error)" : ""}]\n${block.content}`
|
||||
} else if (Array.isArray(block.content)) {
|
||||
return `[Tool Result${block.is_error ? " (Error)" : ""}]\n${block.content
|
||||
.map(this.formatContentBlockToMarkdown)
|
||||
.join("\n")}`
|
||||
} else {
|
||||
return `[Tool Result${block.is_error ? " (Error)" : ""}]`
|
||||
}
|
||||
default:
|
||||
return "[Unexpected content type]"
|
||||
}
|
||||
}
|
||||
|
||||
async postStateToWebview() {
|
||||
const [apiKey, maxRequestsPerTask, claudeMessages, lastShownAnnouncementId] = await Promise.all([
|
||||
this.getSecret("apiKey") as Promise<string | undefined>,
|
||||
|
||||
@@ -28,6 +28,7 @@ export type ClaudeAsk =
|
||||
| "request_limit_reached"
|
||||
| "followup"
|
||||
| "command"
|
||||
| "command_output"
|
||||
| "completion_result"
|
||||
| "tool"
|
||||
| "api_req_failed"
|
||||
@@ -38,13 +39,19 @@ export type ClaudeSay =
|
||||
| "api_req_started"
|
||||
| "api_req_finished"
|
||||
| "text"
|
||||
| "command_output"
|
||||
| "completion_result"
|
||||
| "user_feedback"
|
||||
| "api_req_retried"
|
||||
| "command_output"
|
||||
|
||||
export interface ClaudeSayTool {
|
||||
tool: "editedExistingFile" | "newFileCreated" | "readFile" | "listFiles" | "analyzeProject"
|
||||
tool:
|
||||
| "editedExistingFile"
|
||||
| "newFileCreated"
|
||||
| "readFile"
|
||||
| "listFilesTopLevel"
|
||||
| "listFilesRecursive"
|
||||
| "viewSourceCodeDefinitionsTopLevel"
|
||||
path?: string
|
||||
diff?: string
|
||||
content?: string
|
||||
|
||||
+3
-2
@@ -3,8 +3,9 @@ import { Anthropic } from "@anthropic-ai/sdk"
|
||||
export type ToolName =
|
||||
| "write_to_file"
|
||||
| "read_file"
|
||||
| "analyze_project"
|
||||
| "list_files"
|
||||
| "list_files_top_level"
|
||||
| "list_files_recursive"
|
||||
| "view_source_code_definitions_top_level"
|
||||
| "execute_command"
|
||||
| "ask_followup_question"
|
||||
| "attempt_completion"
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface WebviewMessage {
|
||||
| "askResponse"
|
||||
| "clearTask"
|
||||
| "didShowAnnouncement"
|
||||
| "downloadTask"
|
||||
text?: string
|
||||
askResponse?: ClaudeAskResponse
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ const Announcement = ({ hideAnnouncement }: AnnouncementProps) => {
|
||||
style={{ position: "absolute", top: "8px", right: "8px" }}>
|
||||
<span className="codicon codicon-close"></span>
|
||||
</VSCodeButton>
|
||||
<h3 style={{ margin: "0 0 8px" }}>🎉{" "}New in v1.0.0</h3>
|
||||
<h3 style={{ margin: "0 0 8px" }}>🎉{" "}New in v1.0.7</h3>
|
||||
<ul style={{ margin: "0 0 8px", paddingLeft: "20px" }}>
|
||||
<li>
|
||||
Open in the editor (using{" "}
|
||||
@@ -33,22 +33,27 @@ const Announcement = ({ hideAnnouncement }: AnnouncementProps) => {
|
||||
workspace more clearly
|
||||
</li>
|
||||
<li>
|
||||
New <code>analyze_project</code> tool to help Claude get a comprehensive overview of your project's
|
||||
source code definitions and file structure
|
||||
New <code style={{ wordBreak: "break-all" }}>list_files_recursive</code> and{" "}
|
||||
<code style={{ wordBreak: "break-all" }}>view_source_code_definitions_top_level</code> tools to help
|
||||
Claude get a comprehensive overview of your project's file structure and source code definitions
|
||||
<VSCodeLink
|
||||
href="https://github.com/saoudrizwan/claude-dev?tab=readme-ov-file#working-in-existing-projects"
|
||||
style={{ display: "inline" }}>
|
||||
(more on this here)
|
||||
</VSCodeLink>
|
||||
</li>
|
||||
<li>Provide feedback to tool use like terminal commands and file edits</li>
|
||||
<li>
|
||||
Updated max output tokens to 8192 so less lazy coding (<code>{"// rest of code here..."}</code>)
|
||||
Interact with CLI commands by sending messages to stdin and terminating long-running processes like
|
||||
servers
|
||||
</li>
|
||||
<li>Provide feedback to tool use like editing files or running commands</li>
|
||||
<li>Added ability to retry failed API requests (helpful for rate limits)</li>
|
||||
<li>
|
||||
Quality of life improvements like markdown rendering, memory optimizations, better theme support
|
||||
</li>
|
||||
<li>Export task to a markdown file (useful as context for future tasks)</li>
|
||||
</ul>
|
||||
<p style={{ margin: "0" }}>
|
||||
Subscribe to my new YouTube to see how to get the most out of Claude Dev!{" "}
|
||||
<VSCodeLink href="https://youtube.com/@saoudrizwan" style={{ display: "inline" }}>
|
||||
https://youtube.com/@saoudrizwan
|
||||
Follow me for more updates!{" "}
|
||||
<VSCodeLink href="https://x.com/sdrzn" style={{ display: "inline" }}>
|
||||
@sdrzn
|
||||
</VSCodeLink>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,8 @@ interface ChatRowProps {
|
||||
syntaxHighlighterStyle: SyntaxHighlighterStyle
|
||||
isExpanded: boolean
|
||||
onToggleExpand: () => void
|
||||
apiRequestFailedMessage?: string
|
||||
lastModifiedMessage?: ClaudeMessage
|
||||
isLast: boolean
|
||||
}
|
||||
|
||||
const ChatRow: React.FC<ChatRowProps> = ({
|
||||
@@ -20,15 +21,37 @@ const ChatRow: React.FC<ChatRowProps> = ({
|
||||
syntaxHighlighterStyle,
|
||||
isExpanded,
|
||||
onToggleExpand,
|
||||
apiRequestFailedMessage,
|
||||
lastModifiedMessage,
|
||||
isLast,
|
||||
}) => {
|
||||
const cost = message.text != null && message.say === "api_req_started" ? JSON.parse(message.text).cost : undefined
|
||||
const apiRequestFailedMessage =
|
||||
isLast && lastModifiedMessage?.ask === "api_req_failed" // if request is retried then the latest message is a api_req_retried
|
||||
? lastModifiedMessage?.text
|
||||
: undefined
|
||||
const isCommandExecuting =
|
||||
isLast && lastModifiedMessage?.ask === "command" && lastModifiedMessage?.text?.includes(COMMAND_OUTPUT_STRING)
|
||||
|
||||
const getIconAndTitle = (type: ClaudeAsk | ClaudeSay | undefined): [JSX.Element | null, JSX.Element | null] => {
|
||||
const normalColor = "var(--vscode-foreground)"
|
||||
const errorColor = "var(--vscode-errorForeground)"
|
||||
const successColor = "var(--vscode-testing-iconPassed)"
|
||||
|
||||
const ProgressIndicator = (
|
||||
<div
|
||||
style={{
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}>
|
||||
<div style={{ transform: "scale(0.55)", transformOrigin: "center" }}>
|
||||
<VSCodeProgressRing />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
switch (type) {
|
||||
case "request_limit_reached":
|
||||
return [
|
||||
@@ -46,9 +69,13 @@ const ChatRow: React.FC<ChatRowProps> = ({
|
||||
]
|
||||
case "command":
|
||||
return [
|
||||
<span
|
||||
className="codicon codicon-terminal"
|
||||
style={{ color: normalColor, marginBottom: "-1.5px" }}></span>,
|
||||
isCommandExecuting ? (
|
||||
ProgressIndicator
|
||||
) : (
|
||||
<span
|
||||
className="codicon codicon-terminal"
|
||||
style={{ color: normalColor, marginBottom: "-1.5px" }}></span>
|
||||
),
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>
|
||||
Claude wants to execute this command:
|
||||
</span>,
|
||||
@@ -71,18 +98,7 @@ const ChatRow: React.FC<ChatRowProps> = ({
|
||||
className="codicon codicon-error"
|
||||
style={{ color: errorColor, marginBottom: "-1.5px" }}></span>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}>
|
||||
<div style={{ transform: "scale(0.55)", transformOrigin: "center" }}>
|
||||
<VSCodeProgressRing />
|
||||
</div>
|
||||
</div>
|
||||
ProgressIndicator
|
||||
),
|
||||
cost ? (
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>API Request Complete</span>
|
||||
@@ -92,6 +108,13 @@ const ChatRow: React.FC<ChatRowProps> = ({
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>Making API Request...</span>
|
||||
),
|
||||
]
|
||||
case "followup":
|
||||
return [
|
||||
<span
|
||||
className="codicon codicon-question"
|
||||
style={{ color: normalColor, marginBottom: "-1.5px" }}></span>,
|
||||
<span style={{ color: normalColor, fontWeight: "bold" }}>Claude has a question:</span>,
|
||||
]
|
||||
default:
|
||||
return [null, null]
|
||||
}
|
||||
@@ -100,77 +123,106 @@ const ChatRow: React.FC<ChatRowProps> = ({
|
||||
const renderMarkdown = (markdown: string = "") => {
|
||||
// react-markdown lets us customize elements, so here we're using their example of replacing code blocks with SyntaxHighlighter. However when there are no language matches (` or ``` without a language specifier) then we default to a normal code element for inline code. Code blocks without a language specifier shouldn't be a common occurrence as we prompt Claude to always use a language specifier.
|
||||
return (
|
||||
<Markdown
|
||||
children={markdown}
|
||||
components={{
|
||||
p(props) {
|
||||
const { style, ...rest } = props
|
||||
return (
|
||||
<p
|
||||
style={{
|
||||
...style,
|
||||
margin: 0,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
},
|
||||
ol(props) {
|
||||
const { style, ...rest } = props
|
||||
return <ol style={{ ...style, padding: "0 0 0 20px", margin: "10px 0" }} {...rest} />
|
||||
},
|
||||
ul(props) {
|
||||
const { style, ...rest } = props
|
||||
return <ul style={{ ...style, padding: "0 0 0 20px", margin: "10px 0" }} {...rest} />
|
||||
},
|
||||
// https://github.com/remarkjs/react-markdown?tab=readme-ov-file#use-custom-components-syntax-highlight
|
||||
code(props) {
|
||||
const { children, className, node, ...rest } = props
|
||||
const match = /language-(\w+)/.exec(className || "")
|
||||
return match ? (
|
||||
<SyntaxHighlighter
|
||||
{...(rest as any)} // will be passed down to pre
|
||||
PreTag="div"
|
||||
children={String(children).replace(/\n$/, "")}
|
||||
language={match[1]}
|
||||
style={{
|
||||
...syntaxHighlighterStyle,
|
||||
'code[class*="language-"]': {
|
||||
background: "var(--vscode-editor-background)",
|
||||
},
|
||||
'pre[class*="language-"]': {
|
||||
background: "var(--vscode-editor-background)",
|
||||
},
|
||||
}}
|
||||
customStyle={{
|
||||
overflowX: "auto",
|
||||
overflowY: "hidden",
|
||||
maxWidth: "100%",
|
||||
margin: 0,
|
||||
padding: "10px",
|
||||
// important to note that min-width: max-content is not required here how it is in CodeBlock.tsx
|
||||
borderRadius: 3,
|
||||
border: "1px solid var(--vscode-sideBar-border)",
|
||||
fontSize: "var(--vscode-editor-font-size)",
|
||||
lineHeight: "var(--vscode-editor-line-height)",
|
||||
fontFamily: "var(--vscode-editor-font-family)",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<code
|
||||
{...rest}
|
||||
className={className}
|
||||
style={{ whiteSpace: "pre-line", wordBreak: "break-word" }}>
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<div style={{ wordBreak: "break-word", overflowWrap: "anywhere" }}>
|
||||
<Markdown
|
||||
children={markdown}
|
||||
components={{
|
||||
p(props) {
|
||||
const { style, ...rest } = props
|
||||
return (
|
||||
<p
|
||||
style={{
|
||||
...style,
|
||||
margin: 0,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
overflowWrap: "anywhere",
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
},
|
||||
ol(props) {
|
||||
const { style, ...rest } = props
|
||||
return (
|
||||
<ol
|
||||
style={{
|
||||
...style,
|
||||
padding: "0 0 0 20px",
|
||||
margin: "10px 0",
|
||||
wordBreak: "break-word",
|
||||
overflowWrap: "anywhere",
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
},
|
||||
ul(props) {
|
||||
const { style, ...rest } = props
|
||||
return (
|
||||
<ul
|
||||
style={{
|
||||
...style,
|
||||
padding: "0 0 0 20px",
|
||||
margin: "10px 0",
|
||||
wordBreak: "break-word",
|
||||
overflowWrap: "anywhere",
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
},
|
||||
// https://github.com/remarkjs/react-markdown?tab=readme-ov-file#use-custom-components-syntax-highlight
|
||||
code(props) {
|
||||
const { children, className, node, ...rest } = props
|
||||
const match = /language-(\w+)/.exec(className || "")
|
||||
return match ? (
|
||||
<SyntaxHighlighter
|
||||
{...(rest as any)} // will be passed down to pre
|
||||
PreTag="div"
|
||||
children={String(children).replace(/\n$/, "")}
|
||||
language={match[1]}
|
||||
style={{
|
||||
...syntaxHighlighterStyle,
|
||||
'code[class*="language-"]': {
|
||||
background: "var(--vscode-editor-background)",
|
||||
},
|
||||
'pre[class*="language-"]': {
|
||||
background: "var(--vscode-editor-background)",
|
||||
},
|
||||
}}
|
||||
customStyle={{
|
||||
overflowX: "auto",
|
||||
overflowY: "hidden",
|
||||
maxWidth: "100%",
|
||||
margin: 0,
|
||||
padding: "10px",
|
||||
// important to note that min-width: max-content is not required here how it is in CodeBlock.tsx
|
||||
borderRadius: 3,
|
||||
border: "1px solid var(--vscode-sideBar-border)",
|
||||
fontSize: "var(--vscode-editor-font-size)",
|
||||
lineHeight: "var(--vscode-editor-line-height)",
|
||||
fontFamily: "var(--vscode-editor-font-family)",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<code
|
||||
{...rest}
|
||||
className={className}
|
||||
style={{
|
||||
whiteSpace: "pre-line",
|
||||
wordBreak: "break-word",
|
||||
overflowWrap: "anywhere",
|
||||
}}>
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -186,8 +238,9 @@ const ChatRow: React.FC<ChatRowProps> = ({
|
||||
|
||||
const pStyle: React.CSSProperties = {
|
||||
margin: 0,
|
||||
whiteSpace: "pre-line",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
overflowWrap: "anywhere",
|
||||
}
|
||||
|
||||
switch (message.type) {
|
||||
@@ -338,13 +391,13 @@ const ChatRow: React.FC<ChatRowProps> = ({
|
||||
/>
|
||||
</>
|
||||
)
|
||||
case "listFiles":
|
||||
case "listFilesTopLevel":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("folder-opened")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
Claude wants to view this directory:
|
||||
Claude wants to view the top level files in this directory:
|
||||
</span>
|
||||
</div>
|
||||
<CodeBlock
|
||||
@@ -357,13 +410,33 @@ const ChatRow: React.FC<ChatRowProps> = ({
|
||||
/>
|
||||
</>
|
||||
)
|
||||
case "analyzeProject":
|
||||
case "listFilesRecursive":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("folder-opened")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
Claude wants to recursively view all files in this directory:
|
||||
</span>
|
||||
</div>
|
||||
<CodeBlock
|
||||
code={tool.content!}
|
||||
path={tool.path!}
|
||||
language="shell-session"
|
||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||
isExpanded={isExpanded}
|
||||
onToggleExpand={onToggleExpand}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
case "viewSourceCodeDefinitionsTopLevel":
|
||||
return (
|
||||
<>
|
||||
<div style={headerStyle}>
|
||||
{toolIcon("file-code")}
|
||||
<span style={{ fontWeight: "bold" }}>
|
||||
Claude wants to analyze this project:
|
||||
Claude wants to view source code definitions in files at the top level
|
||||
of this directory:
|
||||
</span>
|
||||
</div>
|
||||
<CodeBlock
|
||||
|
||||
@@ -108,6 +108,13 @@ const ChatView = ({ messages, isHidden, vscodeThemeName, showAnnouncement, hideA
|
||||
setPrimaryButtonText("Run Command")
|
||||
setSecondaryButtonText("Reject")
|
||||
break
|
||||
case "command_output":
|
||||
setTextAreaDisabled(false)
|
||||
setClaudeAsk("command_output")
|
||||
setEnableButtons(true)
|
||||
setPrimaryButtonText("Exit Command")
|
||||
setSecondaryButtonText(undefined)
|
||||
break
|
||||
case "completion_result":
|
||||
// extension waiting for feedback. but we can just present a new task button
|
||||
setTextAreaDisabled(false)
|
||||
@@ -168,6 +175,7 @@ const ChatView = ({ messages, isHidden, vscodeThemeName, showAnnouncement, hideA
|
||||
case "followup":
|
||||
case "tool":
|
||||
case "command": // user can provide feedback to a tool or command use
|
||||
case "command_output": // user can send input to command stdin
|
||||
case "completion_result": // if this happens then the user has feedback for the completion result
|
||||
vscode.postMessage({ type: "askResponse", askResponse: "textResponse", text })
|
||||
break
|
||||
@@ -191,6 +199,7 @@ const ChatView = ({ messages, isHidden, vscodeThemeName, showAnnouncement, hideA
|
||||
case "request_limit_reached":
|
||||
case "api_req_failed":
|
||||
case "command":
|
||||
case "command_output":
|
||||
case "tool":
|
||||
vscode.postMessage({ type: "askResponse", askResponse: "yesButtonTapped" })
|
||||
break
|
||||
@@ -315,6 +324,13 @@ const ChatView = ({ messages, isHidden, vscodeThemeName, showAnnouncement, hideA
|
||||
return () => clearTimeout(timer)
|
||||
}, [visibleMessages])
|
||||
|
||||
const placeholderText = useMemo(() => {
|
||||
if (messages.at(-1)?.ask === "command_output") {
|
||||
return "Type input to command stdin..."
|
||||
}
|
||||
return task ? "Type a message..." : "Type your task here..."
|
||||
}, [task, messages])
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -378,11 +394,8 @@ const ChatView = ({ messages, isHidden, vscodeThemeName, showAnnouncement, hideA
|
||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||
isExpanded={expandedRows[message.ts] || false}
|
||||
onToggleExpand={() => toggleRowExpansion(message.ts)}
|
||||
apiRequestFailedMessage={
|
||||
index === visibleMessages.length - 1 && modifiedMessages.at(-1)?.ask === "api_req_failed" // if request is retried then the latest message is a api_req_retried
|
||||
? modifiedMessages.at(-1)?.text
|
||||
: undefined
|
||||
}
|
||||
lastModifiedMessage={modifiedMessages.at(-1)}
|
||||
isLast={index === visibleMessages.length - 1}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -425,7 +438,7 @@ const ChatView = ({ messages, isHidden, vscodeThemeName, showAnnouncement, hideA
|
||||
//virtuosoRef.current?.scrollToIndex({ index: "LAST", align: "end", behavior: "auto" })
|
||||
virtuosoRef.current?.scrollTo({ top: Number.MAX_SAFE_INTEGER, behavior: "auto" })
|
||||
}
|
||||
placeholder={task ? "Type a message..." : "Type your task here..."}
|
||||
placeholder={placeholderText}
|
||||
maxRows={10}
|
||||
autoFocus={true}
|
||||
style={{
|
||||
|
||||
@@ -2,6 +2,7 @@ import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
|
||||
import React, { useEffect, useRef, useState } from "react"
|
||||
import TextTruncate from "react-text-truncate"
|
||||
import { useWindowSize } from "react-use"
|
||||
import { vscode } from "../utilities/vscode"
|
||||
|
||||
interface TaskHeaderProps {
|
||||
taskText: string
|
||||
@@ -71,6 +72,10 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({ taskText, tokensIn, tokensOut,
|
||||
|
||||
const toggleExpand = () => setIsExpanded(!isExpanded)
|
||||
|
||||
const handleDownload = () => {
|
||||
vscode.postMessage({ type: "downloadTask" })
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: "15px 15px 10px 15px" }}>
|
||||
<div
|
||||
@@ -82,6 +87,7 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({ taskText, tokensIn, tokensOut,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "8px",
|
||||
position: "relative",
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
@@ -103,6 +109,7 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({ taskText, tokensIn, tokensOut,
|
||||
fontSize: "var(--vscode-font-size)",
|
||||
overflowY: isExpanded ? "auto" : "hidden",
|
||||
wordBreak: "break-word",
|
||||
overflowWrap: "anywhere",
|
||||
}}>
|
||||
<TextTruncate
|
||||
key={textTruncateKey}
|
||||
@@ -135,7 +142,7 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({ taskText, tokensIn, tokensOut,
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "4px" }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "5px" }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
<span style={{ fontWeight: "bold" }}>Tokens:</span>
|
||||
<span style={{ display: "flex", alignItems: "center", gap: "3px" }}>
|
||||
<i
|
||||
@@ -152,9 +159,25 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({ taskText, tokensIn, tokensOut,
|
||||
{tokensIn.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
<span style={{ fontWeight: "bold" }}>API Cost:</span>
|
||||
<span>${totalCost.toFixed(4)}</span>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
|
||||
<span style={{ fontWeight: "bold" }}>API Cost:</span>
|
||||
<span>${totalCost.toFixed(4)}</span>
|
||||
</div>
|
||||
<VSCodeButton
|
||||
appearance="icon"
|
||||
onClick={handleDownload}
|
||||
style={{
|
||||
marginBottom: "-2px",
|
||||
marginRight: "-2.5px",
|
||||
}}>
|
||||
<div style={{ fontSize: "10.5px", fontWeight: "bold", opacity: 0.6 }}>EXPORT .MD</div>
|
||||
</VSCodeButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,8 +14,8 @@ import { ClaudeMessage } from "@shared/ExtensionMessage"
|
||||
* @example
|
||||
* const messages: ClaudeMessage[] = [
|
||||
* { type: 'ask', ask: 'command', text: 'ls', ts: 1625097600000 },
|
||||
* { type: 'say', say: 'command_output', text: 'file1.txt', ts: 1625097601000 },
|
||||
* { type: 'say', say: 'command_output', text: 'file2.txt', ts: 1625097602000 }
|
||||
* { type: 'ask', ask: 'command_output', text: 'file1.txt', ts: 1625097601000 },
|
||||
* { type: 'ask', ask: 'command_output', text: 'file2.txt', ts: 1625097602000 }
|
||||
* ];
|
||||
* const result = simpleCombineCommandSequences(messages);
|
||||
* // Result: [{ type: 'ask', ask: 'command', text: 'ls\nfile1.txt\nfile2.txt', ts: 1625097600000 }]
|
||||
@@ -35,7 +35,7 @@ export function combineCommandSequences(messages: ClaudeMessage[]): ClaudeMessag
|
||||
// Stop if we encounter the next command
|
||||
break
|
||||
}
|
||||
if (messages[j].type === "say" && messages[j].say === "command_output") {
|
||||
if (messages[j].ask === "command_output" || messages[j].say === "command_output") {
|
||||
if (!didAddOutput) {
|
||||
// Add a newline before the first output
|
||||
combinedText += `\n${COMMAND_OUTPUT_STRING}`
|
||||
@@ -57,7 +57,7 @@ export function combineCommandSequences(messages: ClaudeMessage[]): ClaudeMessag
|
||||
|
||||
// Second pass: remove command_outputs and replace original commands with combined ones
|
||||
return messages
|
||||
.filter((msg) => !(msg.type === "say" && msg.say === "command_output"))
|
||||
.filter((msg) => !(msg.ask === "command_output" || msg.say === "command_output"))
|
||||
.map((msg) => {
|
||||
if (msg.type === "ask" && msg.ask === "command") {
|
||||
const combinedCommand = combinedCommands.find((cmd) => cmd.ts === msg.ts)
|
||||
|
||||
Reference in New Issue
Block a user