Compare commits

...
Author SHA1 Message Date
Trevor Hudson dbabc58100 changeset 2025-05-11 10:39:33 -07:00
Trevor Hudson ab5e67000d add ui host 2025-05-11 09:40:16 -07:00
Trevor Hudson 8ad249937f add nocapture 2025-05-10 20:57:58 -07:00
Trevor Hudson f980119e9f fresh install mode 2025-05-10 18:44:37 -07:00
20 changed files with 130 additions and 54 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
introduce front end tracking for those who have opted in to telemetry
+22
View File
@@ -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}"
}
}
]
}
+6
View File
@@ -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": [
+2 -2
View File
@@ -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.
+2 -2
View File
@@ -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,
})
}
+2 -1
View File
@@ -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",
}
+11 -6
View File
@@ -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()
}
+5 -3
View File
@@ -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={{