diff --git a/.changeset/fuzzy-moose-punch.md b/.changeset/fuzzy-moose-punch.md
new file mode 100644
index 0000000000..7034341375
--- /dev/null
+++ b/.changeset/fuzzy-moose-punch.md
@@ -0,0 +1,5 @@
+---
+"claude-dev": patch
+---
+
+Add terminal context mention
diff --git a/src/core/mentions/index.ts b/src/core/mentions/index.ts
index 1c9c122d1d..ea9e2afd79 100644
--- a/src/core/mentions/index.ts
+++ b/src/core/mentions/index.ts
@@ -7,6 +7,7 @@ import fs from "fs/promises"
import { extractTextFromFile } from "../../integrations/misc/extract-text"
import { isBinaryFile } from "isbinaryfile"
import { diagnosticsToProblemsString } from "../../integrations/diagnostics"
+import { getLatestTerminalOutput } from "../../integrations/terminal/get-latest-output"
export function openMention(mention?: string): void {
if (!mention) {
@@ -28,6 +29,8 @@ export function openMention(mention?: string): void {
}
} else if (mention === "problems") {
vscode.commands.executeCommand("workbench.actions.view.problems")
+ } else if (mention === "terminal") {
+ vscode.commands.executeCommand("workbench.action.terminal.focus")
} else if (mention.startsWith("http")) {
vscode.env.openExternal(vscode.Uri.parse(mention))
}
@@ -46,6 +49,8 @@ export async function parseMentions(text: string, cwd: string, urlContentFetcher
: `'${mentionPath}' (see below for file content)`
} else if (mention === "problems") {
return `Workspace Problems (see below for diagnostics)`
+ } else if (mention === "terminal") {
+ return `Terminal Output (see below for output)`
}
return match
})
@@ -99,6 +104,13 @@ export async function parseMentions(text: string, cwd: string, urlContentFetcher
} catch (error) {
parsedText += `\n\n\nError fetching diagnostics: ${error.message}\n`
}
+ } else if (mention === "terminal") {
+ try {
+ const terminalOutput = await getLatestTerminalOutput()
+ parsedText += `\n\n\n${terminalOutput}\n`
+ } catch (error) {
+ parsedText += `\n\n\nError fetching terminal output: ${error.message}\n`
+ }
}
}
diff --git a/src/integrations/terminal/get-latest-output.ts b/src/integrations/terminal/get-latest-output.ts
new file mode 100644
index 0000000000..0c869e7fad
--- /dev/null
+++ b/src/integrations/terminal/get-latest-output.ts
@@ -0,0 +1,45 @@
+import * as vscode from "vscode"
+
+/**
+ * Gets the contents of the active terminal
+ * @returns The terminal contents as a string
+ */
+export async function getLatestTerminalOutput(): Promise {
+ // Store original clipboard content to restore later
+ const originalClipboard = await vscode.env.clipboard.readText()
+
+ try {
+ // Select terminal content
+ await vscode.commands.executeCommand("workbench.action.terminal.selectAll")
+
+ // Copy selection to clipboard
+ await vscode.commands.executeCommand("workbench.action.terminal.copySelection")
+
+ // Clear the selection
+ await vscode.commands.executeCommand("workbench.action.terminal.clearSelection")
+
+ // Get terminal contents from clipboard
+ let terminalContents = (await vscode.env.clipboard.readText()).trim()
+
+ // Check if there's actually a terminal open
+ if (terminalContents === originalClipboard) {
+ return ""
+ }
+
+ // Clean up command separation
+ const lines = terminalContents.split("\n")
+ const lastLine = lines.pop()?.trim()
+ if (lastLine) {
+ let i = lines.length - 1
+ while (i >= 0 && !lines[i].trim().startsWith(lastLine)) {
+ i--
+ }
+ terminalContents = lines.slice(Math.max(i, 0)).join("\n")
+ }
+
+ return terminalContents
+ } finally {
+ // Restore original clipboard content
+ await vscode.env.clipboard.writeText(originalClipboard)
+ }
+}
diff --git a/src/shared/context-mentions.ts b/src/shared/context-mentions.ts
index 3912868b10..5444b903ef 100644
--- a/src/shared/context-mentions.ts
+++ b/src/shared/context-mentions.ts
@@ -25,6 +25,9 @@ Mention regex:
- `problems\b`:
- **Exact Word ('problems')**: Matches the exact word 'problems'.
- **Word Boundary (`\b`)**: Ensures that 'problems' is matched as a whole word and not as part of another word (e.g., 'problematic').
+ - `terminal\b`:
+ - **Exact Word ('terminal')**: Matches the exact word 'terminal'.
+ - **Word Boundary (`\b`)**: Ensures that 'terminal' is matched as a whole word and not as part of another word (e.g., 'terminals').
- `(?=[.,;:!?]?(?=[\s\r\n]|$))`:
- **Positive Lookahead (`(?=...)`)**: Ensures that the match is followed by specific patterns without including them in the match.
@@ -38,11 +41,12 @@ Mention regex:
- Mentions that are file or folder paths starting with '/' and containing any non-whitespace characters (including periods within the path).
- URLs that start with a protocol (like 'http://') followed by any non-whitespace characters (including query parameters).
- The exact word 'problems'.
+ - The exact word 'terminal'.
- It ensures that any trailing punctuation marks (such as ',', '.', '!', etc.) are not included in the matched mention, allowing the punctuation to follow the mention naturally in the text.
- **Global Regex**:
- `mentionRegexGlobal`: Creates a global version of the `mentionRegex` to find all matches within a given string.
*/
-export const mentionRegex = /@((?:\/|\w+:\/\/)[^\s]+?|problems\b)(?=[.,;:!?]?(?=[\s\r\n]|$))/
+export const mentionRegex = /@((?:\/|\w+:\/\/)[^\s]+?|problems\b|terminal\b)(?=[.,;:!?]?(?=[\s\r\n]|$))/
export const mentionRegexGlobal = new RegExp(mentionRegex.source, "g")
diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx
index e86697405f..180332028b 100644
--- a/webview-ui/src/components/chat/ChatTextArea.tsx
+++ b/webview-ui/src/components/chat/ChatTextArea.tsx
@@ -243,6 +243,7 @@ const ChatTextArea = forwardRef(
const queryItems = useMemo(() => {
return [
{ type: ContextMenuOptionType.Problems, value: "problems" },
+ { type: ContextMenuOptionType.Terminal, value: "terminal" },
...filePaths
.map((file) => "/" + file)
.map((path) => ({
@@ -293,6 +294,8 @@ const ChatTextArea = forwardRef(
insertValue = value || ""
} else if (type === ContextMenuOptionType.Problems) {
insertValue = "problems"
+ } else if (type === ContextMenuOptionType.Terminal) {
+ insertValue = "terminal"
}
const { newValue, mentionIndex } = insertMention(textAreaRef.current.value, cursorPosition, insertValue)
diff --git a/webview-ui/src/components/chat/ContextMenu.tsx b/webview-ui/src/components/chat/ContextMenu.tsx
index 25a7c0902f..8c0b28c513 100644
--- a/webview-ui/src/components/chat/ContextMenu.tsx
+++ b/webview-ui/src/components/chat/ContextMenu.tsx
@@ -48,6 +48,8 @@ const ContextMenu: React.FC = ({
switch (option.type) {
case ContextMenuOptionType.Problems:
return Problems
+ case ContextMenuOptionType.Terminal:
+ return Terminal
case ContextMenuOptionType.URL:
return Paste URL to fetch contents
case ContextMenuOptionType.NoResults:
@@ -85,6 +87,8 @@ const ContextMenu: React.FC = ({
return "folder"
case ContextMenuOptionType.Problems:
return "warning"
+ case ContextMenuOptionType.Terminal:
+ return "terminal"
case ContextMenuOptionType.URL:
return "link"
case ContextMenuOptionType.NoResults:
@@ -173,6 +177,7 @@ const ContextMenu: React.FC = ({
/>
)}
{(option.type === ContextMenuOptionType.Problems ||
+ option.type === ContextMenuOptionType.Terminal ||
((option.type === ContextMenuOptionType.File || option.type === ContextMenuOptionType.Folder) &&
option.value)) && (