Compare commits

..

1 Commits

Author SHA1 Message Date
Dennis Bartlett 4d9b345527 Fix Changeset 2025-05-01 16:25:47 -07:00
40 changed files with 192 additions and 7835 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
---
"cline": minor
"claude-dev": minor
---
Add support for custom API request timeout. Previously, timeouts were hardcoded to 30 seconds for providers like Ollama or 15 seconds for OpenRouter and Cline. Now users can set a custom timeout value in milliseconds through the settings interface.
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Remove linear pull request action
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": patch
---
Fix for git commit mentions in repos with no git commits
-5
View File
@@ -1,5 +0,0 @@
---
"claude-dev": minor
---
Add a timeout setting for the terminal connection, allowing users to adjust this if they are having timeout issues
-1
View File
@@ -13,7 +13,6 @@ permissions:
jobs:
codespell:
if: false
name: Check for spelling errors
runs-on: ubuntu-latest
+177
View File
@@ -0,0 +1,177 @@
name: Create Linear Issue on Pull Request
on:
workflow_dispatch:
pull_request:
branches: [main]
types: [opened]
permissions:
pull-requests: write
jobs:
create-linear-issue-on-pull-request:
runs-on: ubuntu-latest
steps:
- name: Check for existing Linear link
id: check-linear
uses: actions/github-script@v6
with:
result-encoding: string
script: |
const pr = context.payload.pull_request;
// 1) PR body
if (/https?:\/\/linear\.app/.test(pr.body||"")) {
return "true";
}
// 2) Any linked GitHub issues?
const res = await github.graphql(
`query($owner:String!,$repo:String!,$prNumber:Int!){
repository(owner:$owner,name:$repo){
pullRequest(number:$prNumber){
closingIssuesReferences(first:10){
nodes{number}
}
}
}
}`,
{
owner: context.repo.owner,
repo: context.repo.repo,
prNumber: pr.number
}
);
for (const {number} of res.repository.pullRequest.closingIssuesReferences.nodes) {
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: number
});
if (comments.data.some(c=>/https?:\/\/linear\.app/.test(c.body))) {
return "true";
}
}
return "false";
- name: Find or create Linear issue via GraphQL
if: steps.check-linear.outputs.result == 'false'
id: linear
uses: actions/github-script@v6
env:
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
with:
result-encoding: string
script: |
const API = 'https://api.linear.app/graphql';
const apiKey = process.env.LINEAR_API_KEY;
// Check if API key exists
if (!apiKey) {
core.setFailed('LINEAR_API_KEY is not set. Please add it to your repository secrets.');
core.setOutput('error', 'true');
core.setOutput('error-message', 'LINEAR_API_KEY is not set. Please add it to your repository secrets.');
return;
}
// Helper to call Linear with error handling
async function gql(q, v) {
try {
const r = await fetch(API, {
method:'POST',
headers:{
'Content-Type':'application/json',
'Authorization': apiKey
},
body: JSON.stringify({ query: q, variables: v })
});
if (!r.ok) {
throw new Error(`Linear API responded with status ${r.status}: ${await r.text()}`);
}
const json = await r.json();
// Check for GraphQL errors
if (json.errors && json.errors.length > 0) {
const errorMessages = json.errors.map(e => e.message).join(', ');
throw new Error(`Linear GraphQL errors: ${errorMessages}`);
}
return json.data;
} catch (error) {
core.error(`Error calling Linear API: ${error.message}`);
throw error;
}
}
try {
// 1) Set team ID
const teamId = "19b9c1b2-5f58-498c-b1bf-23ee8f52a677"
// 2) Look for existing issue by PR URL
const pr = context.payload.pull_request;
const searchData = await gql(
`query($team:ID!,$q:String!){
issues(filter: { team: { id: { eq: $team } } attachments: { some: { url: { eq: $q } } } }){nodes{id,url}}
}`,
{ team: teamId, q: pr.html_url }
);
let issue = searchData.issues.nodes[0];
// 3) Create if missing
if (!issue) {
const createData = await gql(
`mutation($input:IssueCreateInput!){
issueCreate(input:$input){issue{id,url}}
}`,
{
input: {
teamId,
title: `[GITHUB] ${pr.title}`,
description: `${pr.body||''}\n\n${pr.html_url}`,
stateId: "4d9bcba2-6712-47e3-b577-6ec1ee023dc2",
labelIds: ["504e7d60-5037-483f-a9b8-7e298bdf116f"]
}
}
);
issue = createData.issueCreate.issue;
}
// Set output for next steps
core.setOutput('linear-issue-url', issue.url);
core.setOutput('error', 'false');
} catch (error) {
core.setOutput('error', 'true');
core.setOutput('error-message', error.message);
core.setFailed(`Failed to create or find Linear issue: ${error.message}`);
}
- name: Comment PR with Linear link
if: steps.check-linear.outputs.result == 'false'
uses: actions/github-script@v6
with:
script: |
const pr = context.payload.pull_request;
const url = `${{ steps.linear.outputs.linear-issue-url }}`;
const body = `🔗 Linear issue created: ${url}`;
// Fetch existing comments
const { data: comments } = await github.rest.issues.listComments({
...context.repo,
issue_number: pr.number
});
const botComment = comments.find(c =>
c.user.type === "Bot" && c.body.startsWith("🔗 Linear issue created:")
);
if (botComment) {
await github.rest.issues.updateComment({
...context.repo,
comment_id: botComment.id,
body
});
} else {
await github.rest.issues.createComment({
...context.repo,
issue_number: pr.number,
body
});
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 902 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 666 B

-71
View File
@@ -1,71 +0,0 @@
{
"$schema": "https://mintlify.com/docs.json",
"theme": "linden",
"name": "Cline",
"description": "AI-powered coding assistant for VSCode",
"colors": {
"primary": "#9D4EDD",
"light": "#F0E6FF",
"dark": "#000000"
},
"logo": {
"light": "/assets/robot_panel_light.png",
"dark": "/assets/robot_panel_dark.png"
},
"favicon": {
"light": "/assets/robot_panel_light.png",
"dark": "/assets/robot_panel_dark.png"
},
"background": {
"color": {
"light": "#F0E6FF",
"dark": "#000000"
},
"decoration": "gradient"
},
"styling": {
"eyebrows": "breadcrumbs",
"codeblocks": "system"
},
"appearance": {
"default": "system",
"strict": false
},
"fonts": {
"family": "Roboto",
"weight": 400
},
"navbar": {
"links": [
{
"label": "GitHub",
"href": "https://github.com/cline/cline"
},
{
"label": "Discord",
"href": "https://discord.gg/cline"
}
],
"primary": {
"type": "button",
"label": "Install Cline",
"href": "https://cline.bot/install?utm_source=website&utm_medium=header"
}
},
"navigation": {
"pages": ["introduction"]
},
"footer": {
"socials": {
"x": "https://x.com/cline",
"github": "https://github.com/cline/cline",
"discord": "https://discord.gg/cline"
}
},
"search": {
"prompt": "Search Cline documentation..."
},
"contextual": {
"options": ["copy"]
}
}
-4
View File
@@ -1,4 +0,0 @@
---
title: "Hello World"
description: "This is the introduction to the documentation"
---
+2 -7559
View File
File diff suppressed because it is too large Load Diff
+1 -3
View File
@@ -312,8 +312,7 @@
"publish:marketplace:prerelease": "vsce publish --pre-release && ovsx publish --pre-release",
"prepare": "husky",
"changeset": "changeset",
"version-packages": "changeset version",
"docs:preview": "cd docs && mintlify dev"
"version-packages": "changeset version"
},
"devDependencies": {
"@changesets/cli": "^2.27.12",
@@ -339,7 +338,6 @@
"eslint": "^8.57.0",
"grpc-tools": "^1.13.0",
"husky": "^9.1.7",
"mintlify": "^4.0.515",
"npm-run-all": "^4.1.5",
"prettier": "^3.3.3",
"protoc-gen-ts": "^0.8.7",
+2 -26
View File
@@ -136,14 +136,8 @@ export class Controller {
async initTask(task?: string, images?: string[], historyItem?: HistoryItem) {
await this.clearTask() // ensures that an existing task doesn't exist before starting a new one, although this shouldn't be possible since user must clear task before starting a new one
const {
apiConfiguration,
customInstructions,
autoApprovalSettings,
browserSettings,
chatSettings,
shellIntegrationTimeout,
} = await getAllExtensionState(this.context)
const { apiConfiguration, customInstructions, autoApprovalSettings, browserSettings, chatSettings } =
await getAllExtensionState(this.context)
if (autoApprovalSettings) {
const updatedAutoApprovalSettings = {
@@ -165,7 +159,6 @@ export class Controller {
autoApprovalSettings,
browserSettings,
chatSettings,
shellIntegrationTimeout,
customInstructions,
task,
images,
@@ -791,21 +784,6 @@ export class Controller {
}
break
}
case "updateTerminalConnectionTimeout": {
if (message.shellIntegrationTimeout !== undefined) {
const timeout = message.shellIntegrationTimeout
if (typeof timeout === "number" && !isNaN(timeout) && timeout > 0) {
await updateGlobalState(this.context, "shellIntegrationTimeout", timeout)
await this.postStateToWebview()
} else {
console.warn(
`Invalid shell integration timeout value received: ${timeout}. ` + `Expected a positive number.`,
)
}
}
break
}
// Add more switch case statements here as more webview message commands
// are created within the webview context (i.e. inside media/main.js)
}
@@ -1791,7 +1769,6 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
telemetrySetting,
planActSeparateModelsSetting,
globalClineRulesToggles,
shellIntegrationTimeout,
} = await getAllExtensionState(this.context)
const localClineRulesToggles =
@@ -1821,7 +1798,6 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
vscMachineId: vscode.env.machineId,
globalClineRulesToggles: globalClineRulesToggles || {},
localClineRulesToggles: localClineRulesToggles || {},
shellIntegrationTimeout,
}
}
-1
View File
@@ -76,6 +76,5 @@ export type GlobalStateKey =
| "planActSeparateModelsSetting"
| "favoritedModelIds"
| "requestTimeoutMs"
| "shellIntegrationTimeout"
export type LocalStateKey = "localClineRulesToggles"
-3
View File
@@ -126,7 +126,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
favoritedModelIds,
globalClineRulesToggles,
requestTimeoutMs,
shellIntegrationTimeout,
] = await Promise.all([
getGlobalState(context, "apiProvider") as Promise<ApiProvider | undefined>,
getGlobalState(context, "apiModelId") as Promise<string | undefined>,
@@ -201,7 +200,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
getGlobalState(context, "favoritedModelIds") as Promise<string[] | undefined>,
getGlobalState(context, "globalClineRulesToggles") as Promise<ClineRulesToggles | undefined>,
getGlobalState(context, "requestTimeoutMs") as Promise<number | undefined>,
getGlobalState(context, "shellIntegrationTimeout") as Promise<number | undefined>,
])
let apiProvider: ApiProvider
@@ -321,7 +319,6 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
mcpMarketplaceEnabled,
telemetrySetting: telemetrySetting || "unset",
planActSeparateModelsSetting,
shellIntegrationTimeout: shellIntegrationTimeout || 4000,
}
}
-2
View File
@@ -173,7 +173,6 @@ export class Task {
autoApprovalSettings: AutoApprovalSettings,
browserSettings: BrowserSettings,
chatSettings: ChatSettings,
shellIntegrationTimeout: number,
customInstructions?: string,
task?: string,
images?: string[],
@@ -192,7 +191,6 @@ export class Task {
console.error("Failed to initialize ClineIgnoreController:", error)
})
this.terminalManager = new TerminalManager()
this.terminalManager.setShellIntegrationTimeout(shellIntegrationTimeout)
this.urlContentFetcher = new UrlContentFetcher(context)
this.browserSession = new BrowserSession(context, browserSettings)
this.contextManager = new ContextManager()
+7 -29
View File
@@ -39,7 +39,7 @@ const terminalManager = new TerminalManager(context);
const process = terminalManager.runCommand('npm install', '/path/to/project');
process.on('line', (line) => {
console.log(line);
console.log(line);
});
// To wait for the process to complete naturally:
@@ -93,7 +93,6 @@ export class TerminalManager {
private terminalIds: Set<number> = new Set()
private processes: Map<number, TerminalProcess> = new Map()
private disposables: vscode.Disposable[] = []
private shellIntegrationTimeout: number = 4000
constructor() {
let disposable: vscode.Disposable | undefined
@@ -145,30 +144,13 @@ export class TerminalManager {
process.run(terminalInfo.terminal, command)
} else {
// docs recommend waiting 3s for shell integration to activate
console.log(
`[TerminalManager Test] Waiting for shell integration for terminal ${terminalInfo.id} with timeout ${this.shellIntegrationTimeout}ms`,
)
pWaitFor(() => terminalInfo.terminal.shellIntegration !== undefined, {
timeout: this.shellIntegrationTimeout,
pWaitFor(() => terminalInfo.terminal.shellIntegration !== undefined, { timeout: 4000 }).finally(() => {
const existingProcess = this.processes.get(terminalInfo.id)
if (existingProcess && existingProcess.waitForShellIntegration) {
existingProcess.waitForShellIntegration = false
existingProcess.run(terminalInfo.terminal, command)
}
})
.then(() => {
console.log(
`[TerminalManager Test] Shell integration activated for terminal ${terminalInfo.id} within timeout.`,
)
})
.catch((err) => {
console.warn(
`[TerminalManager Test] Shell integration timed out or failed for terminal ${terminalInfo.id}: ${err.message}`,
)
})
.finally(() => {
console.log(`[TerminalManager Test] Proceeding with command execution for terminal ${terminalInfo.id}.`)
const existingProcess = this.processes.get(terminalInfo.id)
if (existingProcess && existingProcess.waitForShellIntegration) {
existingProcess.waitForShellIntegration = false
existingProcess.run(terminalInfo.terminal, command)
}
})
}
return mergePromise(process, promise)
@@ -237,8 +219,4 @@ export class TerminalManager {
this.disposables.forEach((disposable) => disposable.dispose())
this.disposables = []
}
setShellIntegrationTimeout(timeout: number): void {
this.shellIntegrationTimeout = timeout
}
}
-1
View File
@@ -132,7 +132,6 @@ export interface ExtensionState {
shouldShowAnnouncement: boolean
taskHistory: HistoryItem[]
telemetrySetting: TelemetrySetting
shellIntegrationTimeout: number
uriScheme?: string
userInfo?: {
displayName: string | null
-2
View File
@@ -71,7 +71,6 @@ export interface WebviewMessage {
| "toggleClineRule"
| "deleteClineRule"
| "copyToClipboard"
| "updateTerminalConnectionTimeout"
// | "relaunchChromeDebugMode"
text?: string
@@ -122,7 +121,6 @@ export interface WebviewMessage {
filename?: string
offset?: number
shellIntegrationTimeout?: number
}
export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "messageResponse"
+2 -30
View File
@@ -30,15 +30,6 @@ async function checkGitInstalled(): Promise<boolean> {
}
}
async function checkGitRepoHasCommits(cwd: string): Promise<boolean> {
try {
await execAsync("git rev-parse HEAD", { cwd })
return true
} catch (error) {
return false
}
}
export async function searchCommits(query: string, cwd: string): Promise<GitCommit[]> {
try {
const isInstalled = await checkGitInstalled()
@@ -53,12 +44,6 @@ export async function searchCommits(query: string, cwd: string): Promise<GitComm
return []
}
// Check if repo has any commits
if (!(await checkGitRepoHasCommits(cwd))) {
// No commits yet in the repository
return []
}
// Search commits by hash or message, limiting to 10 results
const { stdout } = await execAsync(
`git log -n 10 --format="%H%n%h%n%s%n%an%n%ad" --date=short ` + `--grep="${query}" --regexp-ignore-case`,
@@ -115,11 +100,6 @@ export async function getCommitInfo(hash: string, cwd: string): Promise<string>
return "Not a git repository"
}
// Check if repo has any commits
if (!(await checkGitRepoHasCommits(cwd))) {
return "Repository has no commits yet"
}
// Get commit info, stats, and diff separately
const { stdout: info } = await execAsync(`git show --format="%H%n%h%n%s%n%an%n%ad%n%b" --no-patch ${hash}`, {
cwd,
@@ -167,16 +147,8 @@ export async function getWorkingState(cwd: string): Promise<string> {
return "No changes in working directory"
}
// Check if repo has any commits before trying to diff against HEAD
let diff = ""
if (await checkGitRepoHasCommits(cwd)) {
// Only run git diff if there are commits
const { stdout: diffOutput } = await execAsync("git diff HEAD", { cwd })
diff = diffOutput
} else {
// No commits yet, use status output only
return `Working directory changes (new repository):\n\n${status}`
}
// Get all changes (both staged and unstaged) compared to HEAD
const { stdout: diff } = await execAsync("git diff HEAD", { cwd })
const output = `Working directory changes:\n\n${status}\n\n${diff}`.trim()
return truncateOutput(output)
} catch (error) {
@@ -9,7 +9,6 @@ import { TabButton } from "../mcp/configuration/McpConfigurationView"
import { useEvent } from "react-use"
import { ExtensionMessage } from "@shared/ExtensionMessage"
import BrowserSettingsSection from "./BrowserSettingsSection"
import TerminalSettingsSection from "./TerminalSettingsSection"
const { IS_DEV } = process.env
@@ -241,9 +240,6 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
{/* Browser Settings Section */}
<BrowserSettingsSection />
{/* Terminal Settings Section */}
<TerminalSettingsSection />
<div className="mt-auto pr-2 flex justify-center">
<SettingsButton
onClick={() => vscode.postMessage({ type: "openExtensionSettings" })}
@@ -1,76 +0,0 @@
import React, { useState } from "react"
import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
import { useExtensionState } from "@/context/ExtensionStateContext"
import { vscode } from "@/utils/vscode"
export const TerminalSettingsSection: React.FC = () => {
const { shellIntegrationTimeout, setShellIntegrationTimeout } = useExtensionState()
const [inputValue, setInputValue] = useState((shellIntegrationTimeout / 1000).toString())
const [inputError, setInputError] = useState<string | null>(null)
const handleTimeoutChange = (event: Event) => {
const target = event.target as HTMLInputElement
const value = target.value
setInputValue(value)
const seconds = parseFloat(value)
if (isNaN(seconds) || seconds <= 0) {
setInputError("Please enter a positive number")
return
}
setInputError(null)
const timeout = Math.round(seconds * 1000) // Convert to milliseconds
// Update local state
setShellIntegrationTimeout(timeout)
// Send to extension
vscode.postMessage({
type: "updateTerminalConnectionTimeout",
shellIntegrationTimeout: timeout,
})
}
const handleInputBlur = () => {
// If there was an error, reset the input to the current valid value
if (inputError) {
setInputValue((shellIntegrationTimeout / 1000).toString())
setInputError(null)
}
}
return (
<div
id="terminal-settings-section"
style={{ marginBottom: 20, borderTop: "1px solid var(--vscode-panel-border)", paddingTop: 15 }}>
<h3 style={{ color: "var(--vscode-foreground)", margin: "0 0 10px 0", fontSize: "14px" }}>Terminal Settings</h3>
<div style={{ marginBottom: 15 }}>
<div style={{ marginBottom: 8 }}>
<label style={{ fontWeight: "500", display: "block", marginBottom: 5 }}>
Shell integration timeout (seconds)
</label>
<div style={{ display: "flex", alignItems: "center" }}>
<VSCodeTextField
style={{ width: "100%" }}
value={inputValue}
placeholder="Enter timeout in seconds"
onChange={(event) => handleTimeoutChange(event as Event)}
onBlur={handleInputBlur}
/>
</div>
{inputError && (
<div style={{ color: "var(--vscode-errorForeground)", fontSize: "12px", marginTop: 5 }}>{inputError}</div>
)}
</div>
<p style={{ fontSize: "12px", color: "var(--vscode-descriptionForeground)", margin: 0 }}>
Set how long Cline waits for shell integration to activate before executing commands. Increase this value if
you experience terminal connection timeouts.
</p>
</div>
</div>
)
}
export default TerminalSettingsSection
@@ -39,7 +39,6 @@ interface ExtensionStateContextType extends ExtensionState {
setTelemetrySetting: (value: TelemetrySetting) => void
setShowAnnouncement: (value: boolean) => void
setPlanActSeparateModelsSetting: (value: boolean) => void
setShellIntegrationTimeout: (value: number) => void
setMcpServers: (value: McpServer[]) => void
// Navigation
@@ -70,7 +69,6 @@ export const ExtensionStateContextProvider: React.FC<{
planActSeparateModelsSetting: true,
globalClineRulesToggles: {},
localClineRulesToggles: {},
shellIntegrationTimeout: 4000, // default timeout for shell integration
})
const [didHydrateState, setDidHydrateState] = useState(false)
const [showWelcome, setShowWelcome] = useState(false)
@@ -244,11 +242,6 @@ export const ExtensionStateContextProvider: React.FC<{
...prevState,
shouldShowAnnouncement: value,
})),
setShellIntegrationTimeout: (value) =>
setState((prevState) => ({
...prevState,
shellIntegrationTimeout: value,
})),
setMcpServers: (mcpServers: McpServer[]) => setMcpServers(mcpServers),
setShowMcp,
setMcpTab,