mirror of
https://github.com/cline/cline.git
synced 2026-09-11 05:47:07 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbabc58100 | ||
|
|
ab5e67000d | ||
|
|
8ad249937f | ||
|
|
f980119e9f |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
introduce front end tracking for those who have opted in to telemetry
|
||||
Vendored
+22
@@ -16,6 +16,28 @@
|
||||
"IS_DEV": "true",
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Run Extension (Fresh Install Mode)",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--profile-temp",
|
||||
"--sync",
|
||||
"off",
|
||||
"--disable-extensions",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"preLaunchTask": "clean-sandbox",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"postDebugTask": "stop",
|
||||
"env": {
|
||||
"IS_DEV": "true",
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+6
@@ -185,6 +185,12 @@
|
||||
"label": "stop",
|
||||
"command": "echo ${input:terminate}",
|
||||
"type": "shell"
|
||||
},
|
||||
{
|
||||
"label": "clean-sandbox",
|
||||
"type": "shell",
|
||||
"dependsOn": ["watch"],
|
||||
"command": "rm -rf .vscode-dev"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
|
||||
@@ -26,9 +26,9 @@ For complete transparency, you can inspect our [telemetry implementation](https:
|
||||
|
||||
### How to Opt Out
|
||||
|
||||
Telemetry in Cline is entirely optional and requires your explicit consent:
|
||||
Telemetry in Cline is entirely optional:
|
||||
|
||||
- When you update or install our VS Code extension, you'll see a simple prompt: "Help Improve Cline" with Allow or Deny options
|
||||
- When you update or install our VS Code extension, you'll see a message about our anonymous telemetry
|
||||
- You can change your preference anytime in settings
|
||||
|
||||
Cline also respects VS Code's global telemetry settings. If you've disabled telemetry at the VS Code level, Cline's telemetry will automatically be disabled as well.
|
||||
|
||||
@@ -249,7 +249,7 @@ export class Controller {
|
||||
// If user already opted in to telemetry, enable telemetry service
|
||||
this.getStateToPostToWebview().then((state) => {
|
||||
const { telemetrySetting } = state
|
||||
const isOptedIn = telemetrySetting === "enabled"
|
||||
const isOptedIn = telemetrySetting !== "disabled"
|
||||
telemetryService.updateTelemetryState(isOptedIn)
|
||||
})
|
||||
break
|
||||
@@ -676,7 +676,7 @@ export class Controller {
|
||||
|
||||
async updateTelemetrySetting(telemetrySetting: TelemetrySetting) {
|
||||
await updateGlobalState(this.context, "telemetrySetting", telemetrySetting)
|
||||
const isOptedIn = telemetrySetting === "enabled"
|
||||
const isOptedIn = telemetrySetting !== "disabled"
|
||||
telemetryService.updateTelemetryState(isOptedIn)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ class PostHogClientProvider {
|
||||
this.client = new PostHog(posthogConfig.apiKey, {
|
||||
host: posthogConfig.host,
|
||||
enableExceptionAutocapture: false,
|
||||
defaultOptIn: false,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Public PostHog key (safe for open source)
|
||||
export const posthogConfig = {
|
||||
apiKey: "phc_qfOAGxZw2TL5O8p9KYd9ak3bPBFzfjC8fy5L6jNWY7K",
|
||||
host: "https://us.i.posthog.com",
|
||||
host: "https://data.cline.bot",
|
||||
uiHost: "https://us.posthog.com",
|
||||
}
|
||||
|
||||
@@ -6,15 +6,20 @@ import { useExtensionState } from "./context/ExtensionStateContext"
|
||||
|
||||
export function CustomPostHogProvider({ children }: { children: ReactNode }) {
|
||||
const { telemetrySetting } = useExtensionState()
|
||||
const isTelemetryEnabled = telemetrySetting === "enabled"
|
||||
const isTelemetryEnabled = telemetrySetting !== "disabled"
|
||||
|
||||
useEffect(() => {
|
||||
posthog.init(posthogConfig.apiKey, {
|
||||
api_host: posthogConfig.host,
|
||||
ui_host: posthogConfig.uiHost,
|
||||
opt_out_capturing_by_default: true,
|
||||
disable_session_recording: true,
|
||||
capture_pageview: false,
|
||||
capture_dead_clicks: true,
|
||||
})
|
||||
|
||||
if (isTelemetryEnabled) {
|
||||
posthog.init(posthogConfig.apiKey, {
|
||||
api_host: posthogConfig.host,
|
||||
autocapture: false,
|
||||
disable_session_recording: true,
|
||||
})
|
||||
posthog.opt_in_capturing()
|
||||
} else {
|
||||
posthog.opt_out_capturing()
|
||||
}
|
||||
|
||||
@@ -378,7 +378,7 @@ export const ChatRowContent = ({
|
||||
marginBottom: "-1.5px",
|
||||
}}></span>
|
||||
),
|
||||
<span style={{ color: normalColor, fontWeight: "bold", wordBreak: "break-word" }}>
|
||||
<span className="ph-no-capture" style={{ color: normalColor, fontWeight: "bold", wordBreak: "break-word" }}>
|
||||
Cline wants to {mcpServerUse.type === "use_mcp_tool" ? "use a tool" : "access a resource"} on the{" "}
|
||||
<code style={{ wordBreak: "break-all" }}>
|
||||
{getMcpServerDisplayName(mcpServerUse.serverName, mcpMarketplaceCatalog)}
|
||||
@@ -493,7 +493,7 @@ export const ChatRowContent = ({
|
||||
}
|
||||
const toolIcon = (name: string, color?: string, rotation?: number, title?: string) => (
|
||||
<span
|
||||
className={`codicon codicon-${name}`}
|
||||
className={`codicon codicon-${name} ph-no-capture`}
|
||||
style={{
|
||||
color: color ? colorMap[color as keyof typeof colorMap] || color : "var(--vscode-foreground)",
|
||||
marginBottom: "-1.5px",
|
||||
@@ -577,6 +577,7 @@ export const ChatRowContent = ({
|
||||
}}>
|
||||
{tool.path?.startsWith(".") && <span>.</span>}
|
||||
<span
|
||||
className="ph-no-capture"
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
@@ -999,12 +1000,13 @@ export const ChatRowContent = ({
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
{message.text}
|
||||
<span className="ph-no-capture">{message.text}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<span style={{ fontWeight: "bold", marginRight: "4px" }}>Thinking:</span>
|
||||
<span
|
||||
className="ph-no-capture"
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
|
||||
@@ -93,8 +93,11 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
||||
if (option.value) {
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 0 }}>
|
||||
<span style={{ lineHeight: "1.2" }}>{option.label}</span>
|
||||
<span className="ph-no-capture" style={{ lineHeight: "1.2" }}>
|
||||
{option.label}
|
||||
</span>
|
||||
<span
|
||||
className="ph-no-capture"
|
||||
style={{
|
||||
fontSize: "0.85em",
|
||||
opacity: 0.7,
|
||||
@@ -118,6 +121,7 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
||||
<span>/</span>
|
||||
{option.value?.startsWith("/.") && <span>.</span>}
|
||||
<span
|
||||
className="ph-no-capture"
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
|
||||
@@ -51,6 +51,8 @@ export const OptionsButtons = ({
|
||||
</div> */}
|
||||
{options.map((option, index) => (
|
||||
<OptionButton
|
||||
id={`options-button-${index}`}
|
||||
className="options-button"
|
||||
key={index}
|
||||
isSelected={option === selected}
|
||||
isNotSelectable={hasSelected || !isActive}
|
||||
@@ -63,7 +65,7 @@ export const OptionsButtons = ({
|
||||
text: option + (inputValue ? `: ${inputValue?.trim()}` : ""),
|
||||
})
|
||||
}}>
|
||||
{option}
|
||||
<span className="ph-no-capture">{option}</span>
|
||||
</OptionButton>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -51,7 +51,8 @@ const SlashCommandMenu: React.FC<SlashCommandMenuProps> = ({ onSelect, selectedI
|
||||
filteredCommands.map((command, index) => (
|
||||
<div
|
||||
key={command.name}
|
||||
className={`py-2 px-3 cursor-pointer flex flex-col border-b border-[var(--vscode-editorGroup-border)] ${
|
||||
id={`slash-command-menu-item-${index}`}
|
||||
className={`slash-command-menu-item py-2 px-3 cursor-pointer flex flex-col border-b border-[var(--vscode-editorGroup-border)] ${
|
||||
// Corrected padding
|
||||
index === selectedIndex
|
||||
? "bg-[var(--vscode-quickInputList-focusBackground)] text-[var(--vscode-quickInputList-focusForeground)]"
|
||||
@@ -59,9 +60,11 @@ const SlashCommandMenu: React.FC<SlashCommandMenuProps> = ({ onSelect, selectedI
|
||||
} hover:bg-[var(--vscode-list-hoverBackground)]`}
|
||||
onClick={() => handleClick(command)}
|
||||
onMouseEnter={() => setSelectedIndex(index)}>
|
||||
<div className="font-bold whitespace-nowrap overflow-hidden text-ellipsis">/{command.name}</div>
|
||||
<div className="font-bold whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
<span className="ph-no-capture">/{command.name}</span>
|
||||
</div>
|
||||
<div className="text-[0.85em] text-[var(--vscode-descriptionForeground)] whitespace-normal overflow-hidden text-ellipsis">
|
||||
{command.description}
|
||||
<span className="ph-no-capture">{command.description}</span>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
|
||||
@@ -256,7 +256,11 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
|
||||
Task
|
||||
{!isTaskExpanded && ":"}
|
||||
</span>
|
||||
{!isTaskExpanded && <span style={{ marginLeft: 4 }}>{highlightText(task.text, false)}</span>}
|
||||
{!isTaskExpanded && (
|
||||
<span className="ph-no-capture" style={{ marginLeft: 4 }}>
|
||||
{highlightText(task.text, false)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{isCostAvailable && (
|
||||
@@ -302,7 +306,7 @@ const TaskHeader: React.FC<TaskHeaderProps> = ({
|
||||
wordBreak: "break-word",
|
||||
overflowWrap: "anywhere",
|
||||
}}>
|
||||
{highlightText(task.text, false)}
|
||||
<span className="ph-no-capture">{highlightText(task.text, false)}</span>
|
||||
</div>
|
||||
{!isTextExpanded && showSeeMore && (
|
||||
<div
|
||||
|
||||
@@ -141,7 +141,9 @@ const UserMessage: React.FC<UserMessageProps> = ({ text, images, messageTs, send
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<span style={{ display: "block" }}>{highlightText(editedText || text)}</span>
|
||||
<span className="ph-no-capture" style={{ display: "block" }}>
|
||||
{highlightText(editedText || text)}
|
||||
</span>
|
||||
)}
|
||||
{images && images.length > 0 && <Thumbnails images={images} style={{ marginTop: "8px" }} />}
|
||||
</div>
|
||||
|
||||
@@ -74,7 +74,7 @@ const RuleRow: React.FC<{
|
||||
}`}>
|
||||
<span className="flex-1 overflow-hidden break-all whitespace-normal flex items-center mr-1" title={rulePath}>
|
||||
{getRuleTypeIcon() && <span className="mr-1.5">{getRuleTypeIcon()}</span>}
|
||||
{displayName}
|
||||
<span className="ph-no-capture">{displayName}</span>
|
||||
</span>
|
||||
|
||||
{/* Toggle Switch */}
|
||||
|
||||
@@ -151,7 +151,9 @@ const CodeBlock = memo(({ source, forceWrap = false }: CodeBlockProps) => {
|
||||
maxHeight: forceWrap ? "none" : "100%",
|
||||
backgroundColor: CODE_BLOCK_BG_COLOR,
|
||||
}}>
|
||||
<StyledMarkdown forceWrap={forceWrap}>{reactContent}</StyledMarkdown>
|
||||
<StyledMarkdown className="ph-no-capture" forceWrap={forceWrap}>
|
||||
{reactContent}
|
||||
</StyledMarkdown>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -339,7 +339,7 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<StyledMarkdown>{reactContent}</StyledMarkdown>
|
||||
<StyledMarkdown className="ph-no-capture">{reactContent}</StyledMarkdown>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -12,6 +12,26 @@ const BannerContainer = styled.div`
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 6px;
|
||||
position: relative;
|
||||
`
|
||||
|
||||
const CloseButton = styled.button`
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--vscode-foreground);
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
opacity: 0.7;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
`
|
||||
|
||||
const ButtonContainer = styled.div`
|
||||
@@ -25,31 +45,31 @@ const ButtonContainer = styled.div`
|
||||
`
|
||||
|
||||
const TelemetryBanner = () => {
|
||||
const [hasChosen, setHasChosen] = useState(false)
|
||||
|
||||
const handleAllow = () => {
|
||||
setHasChosen(true)
|
||||
vscode.postMessage({ type: "telemetrySetting", telemetrySetting: "enabled" satisfies TelemetrySetting })
|
||||
}
|
||||
|
||||
const handleDeny = () => {
|
||||
setHasChosen(true)
|
||||
vscode.postMessage({ type: "telemetrySetting", telemetrySetting: "disabled" satisfies TelemetrySetting })
|
||||
}
|
||||
|
||||
const handleOpenSettings = () => {
|
||||
handleClose()
|
||||
vscode.postMessage({ type: "openSettings" })
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
vscode.postMessage({ type: "telemetrySetting", telemetrySetting: "enabled" satisfies TelemetrySetting })
|
||||
}
|
||||
|
||||
return (
|
||||
<BannerContainer>
|
||||
<CloseButton onClick={handleClose} aria-label="Close banner and enable telemetry">
|
||||
✕
|
||||
</CloseButton>
|
||||
<div>
|
||||
<strong>Help Improve Cline</strong>
|
||||
<i>
|
||||
<br />
|
||||
(and access experimental features)
|
||||
</i>
|
||||
<div style={{ marginTop: 4 }}>
|
||||
Send anonymous error and usage data to help us fix bugs and improve the extension. No code, prompts, or
|
||||
personal information is ever sent.
|
||||
Cline collects anonymous error and usage data to help us fix bugs and improve the extension. No code, prompts,
|
||||
or personal information is ever sent.
|
||||
<div style={{ marginTop: 4 }}>
|
||||
You can always change this in{" "}
|
||||
You can turn this setting off in{" "}
|
||||
<VSCodeLink href="#" onClick={handleOpenSettings}>
|
||||
settings
|
||||
</VSCodeLink>
|
||||
@@ -57,14 +77,6 @@ const TelemetryBanner = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ButtonContainer>
|
||||
<VSCodeButton appearance="primary" onClick={handleAllow} disabled={hasChosen}>
|
||||
Allow
|
||||
</VSCodeButton>
|
||||
<VSCodeButton appearance="secondary" onClick={handleDeny} disabled={hasChosen}>
|
||||
Deny
|
||||
</VSCodeButton>
|
||||
</ButtonContainer>
|
||||
</BannerContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -105,6 +105,8 @@ const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
|
||||
)}
|
||||
|
||||
<div
|
||||
id={`history-preview-task-${item.id}`}
|
||||
className="history-preview-task"
|
||||
style={{
|
||||
fontSize: "var(--vscode-font-size)",
|
||||
color: "var(--vscode-descriptionForeground)",
|
||||
@@ -117,7 +119,7 @@ const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
|
||||
wordBreak: "break-word",
|
||||
overflowWrap: "anywhere",
|
||||
}}>
|
||||
{item.task}
|
||||
<span className="ph-no-capture">{item.task}</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
|
||||
@@ -514,11 +514,14 @@ const HistoryView = ({ onDone }: HistoryViewProps) => {
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
overflowWrap: "anywhere",
|
||||
}}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: item.task,
|
||||
}}
|
||||
/>
|
||||
}}>
|
||||
<span
|
||||
className="ph-no-capture"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: item.task,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
|
||||
Reference in New Issue
Block a user