Compare commits

...

4 Commits

Author SHA1 Message Date
Trevor Hudson 264e076621 environment variables 2025-02-25 14:31:13 -08:00
Trevor Hudson ba482c1d37 add changeset 2025-02-24 16:54:09 -08:00
Trevor Hudson 872123bfe4 move capture 2025-02-24 16:50:07 -08:00
Trevor Hudson 6bf80959f0 add optional telemetry 2025-02-24 16:40:36 -08:00
22 changed files with 394 additions and 14 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
Include optional telemtry to help cline improve
+2
View File
@@ -82,6 +82,8 @@ jobs:
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
OVSX_PAT: ${{ secrets.OVSX_PAT }}
POSTHOG_PROJECT_API_KEY: ${{ secrets.POSTHOG_PROJECT_API_KEY }}
POSTHOG_INSTANCE_ADDRESS: ${{ secrets.POSTHOG_INSTANCE_ADDRESS }}
run: |
# Required to generate the .vsix
vsce package --out "cline-${{ steps.get_version.outputs.version }}.vsix"
+7 -1
View File
@@ -7,4 +7,10 @@ tmp
.DS_Store
pnpm-lock.yaml
pnpm-lock.yaml
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
+2 -1
View File
@@ -9,6 +9,7 @@ Cline Bot Inc. ("Cline," "we," "our," and/or "us") values the privacy of individ
- Your data is only sent to your chosen AI provider (e.g., Anthropic, OpenAI) when you explicitly request assistance
- All processing happens locally on your machine
- API keys are stored securely in VS Code's built-in settings storage
- Telemetry is collected anonymously via PostHog if the user opts in
## Information We Process
@@ -43,7 +44,7 @@ Cline functions solely as a client-side VS Code extension that facilitates commu
- All operations happen on your local machine
- No central servers or data collection
- No telemetry or usage statistics gathered
- No telemetry or usage statistics gathered unless the user opts in
- No account creation required
2. **API Key Security**:
+7
View File
@@ -1,6 +1,9 @@
const esbuild = require("esbuild")
const fs = require("fs")
const path = require("path")
const dotenv = require("dotenv")
// Load environment variables from .env.local
dotenv.config({ path: ".env" })
const production = process.argv.includes("--production")
const watch = process.argv.includes("--watch")
@@ -78,6 +81,10 @@ const extensionConfig = {
platform: "node",
outfile: "dist/extension.js",
external: ["vscode"],
define: {
"process.env.POSTHOG_PROJECT_API_KEY": JSON.stringify(process.env.POSTHOG_PROJECT_API_KEY || ""),
"process.env.POSTHOG_INSTANCE_ADDRESS": JSON.stringify(process.env.POSTHOG_INSTANCE_ADDRESS || ""),
},
}
async function main() {
+29 -2
View File
@@ -1,12 +1,12 @@
{
"name": "claude-dev",
"version": "3.4.3",
"version": "3.4.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "claude-dev",
"version": "3.4.3",
"version": "3.4.6",
"license": "Apache-2.0",
"dependencies": {
"@anthropic-ai/bedrock-sdk": "^0.10.2",
@@ -40,6 +40,7 @@
"os-name": "^6.0.0",
"p-wait-for": "^5.0.2",
"pdf-parse": "^1.1.1",
"posthog-node": "^4.7.0",
"puppeteer-chromium-resolver": "^23.0.0",
"puppeteer-core": "^23.4.0",
"serialize-error": "^11.0.3",
@@ -63,6 +64,7 @@
"@vscode/test-cli": "^0.0.9",
"@vscode/test-electron": "^2.4.0",
"chai": "^4.3.10",
"dotenv": "^16.4.7",
"esbuild": "^0.25.0",
"eslint": "^8.57.0",
"husky": "^9.1.7",
@@ -7648,6 +7650,19 @@
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/dotenv": {
"version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/duck": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/duck/-/duck-0.1.12.tgz",
@@ -11606,6 +11621,18 @@
"node": ">= 0.4"
}
},
"node_modules/posthog-node": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-4.7.0.tgz",
"integrity": "sha512-RgdUKSW8MfMOkjUa8cYVqWndNjPePNuuxlGbrZC6z1WRBsVc6TdGl8caidmC10RW8mu/BOfmrGbP4cRTo2jARg==",
"license": "MIT",
"dependencies": {
"axios": "^1.7.4"
},
"engines": {
"node": ">=15.0.0"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+7
View File
@@ -186,6 +186,11 @@
"type": "boolean",
"default": true,
"description": "Controls whether the MCP Marketplace is enabled."
},
"cline.enableTelemetry": {
"type": "boolean",
"default": false,
"description": "Enables extension to post anonymous error tracking & usage data to help improve the product."
}
}
}
@@ -228,6 +233,7 @@
"@vscode/test-cli": "^0.0.9",
"@vscode/test-electron": "^2.4.0",
"chai": "^4.3.10",
"dotenv": "^16.4.7",
"esbuild": "^0.25.0",
"eslint": "^8.57.0",
"husky": "^9.1.7",
@@ -268,6 +274,7 @@
"os-name": "^6.0.0",
"p-wait-for": "^5.0.2",
"pdf-parse": "^1.1.1",
"posthog-node": "^4.7.0",
"puppeteer-chromium-resolver": "^23.0.0",
"puppeteer-core": "^23.4.0",
"serialize-error": "^11.0.3",
+32
View File
@@ -59,6 +59,7 @@ import { formatResponse } from "./prompts/responses"
import { addUserInstructions, SYSTEM_PROMPT } from "./prompts/system"
import { getNextTruncationRange, getTruncatedMessages } from "./sliding-window"
import { ClineProvider, GlobalFileNames } from "./webview/ClineProvider"
import posthog from "../services/analytics/PostHogClient"
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution
@@ -148,6 +149,16 @@ export class Cline {
} else {
throw new Error("Either historyItem or task/images must be provided")
}
// capture start of thread with the state at the beginning
posthog.capture({
event: "cline created",
properties: {
taskId: this.taskId,
isHistory: !!historyItem,
chatMode: this.chatSettings.mode,
hasImages: !!images,
},
})
}
updateBrowserSettings(browserSettings: BrowserSettings) {
@@ -3265,6 +3276,27 @@ export class Cline {
this.consecutiveMistakeCount++
}
posthog.capture({
event: "message sent",
properties: {
taskId: this.taskId,
chatMode: this.chatSettings.mode,
apiConversationHistoryCount: this.apiConversationHistory.length,
userMessageCount: this.apiConversationHistory.filter((m) => m.role === "user").length,
assistantMessageCount: this.apiConversationHistory.filter((m) => m.role === "assistant").length,
clineMessageCount: this.clineMessages.length,
textMessageCount: this.clineMessages.filter((m) => !!m.text).length,
askMessageCount: this.clineMessages.filter((m) => !!m.ask).length,
sayMessageCount: this.clineMessages.filter((m) => !!m.say).length,
reasoningMessageCount: this.clineMessages.filter((m) => !!m.reasoning).length,
partialMessageCount: this.clineMessages.filter((m) => !!m.partial).length,
consecutiveMistakeCount: this.consecutiveMistakeCount,
consecutiveAutoApprovedRequestsCount: this.consecutiveAutoApprovedRequestsCount,
toolUseCount: this.clineMessages.filter((m) => m.say === "tool").length,
checkpointsCount: this.clineMessages.filter((m) => m.say === "checkpoint_created").length,
},
})
const recDidEndLoop = await this.recursivelyMakeClineRequests(this.userMessageContent)
didEndLoop = recDidEndLoop
} else {
+15 -2
View File
@@ -94,6 +94,7 @@ type GlobalStateKey =
| "requestyModelId"
| "togetherModelId"
| "mcpMarketplaceCatalog"
| "hideTelemetryOptIn"
export const GlobalFileNames = {
apiConversationHistory: "api_conversation_history.json",
@@ -365,8 +366,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${webview.cspSource}; style-src ${webview.cspSource} 'unsafe-inline'; img-src ${webview.cspSource} https: data:; script-src 'nonce-${nonce}';">
<link rel="stylesheet" type="text/css" href="${stylesUri}">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; connect-src https://*.posthog.com; font-src ${webview.cspSource}; style-src ${webview.cspSource} 'unsafe-inline'; img-src ${webview.cspSource} https: data:; script-src 'nonce-${nonce}' https://*.posthog.com;"> <link rel="stylesheet" type="text/css" href="${stylesUri}">
<link href="${codiconsUri}" rel="stylesheet" />
<title>Cline</title>
</head>
@@ -839,6 +839,12 @@ export class ClineProvider implements vscode.WebviewViewProvider {
)
break
}
case "toggleTelemetryOptin": {
await vscode.workspace.getConfiguration().update("cline.enableTelemetry", true, true)
await this.updateGlobalState("hideTelemetryOptIn", true)
await this.postStateToWebview()
break
}
// Add more switch case statements here as more webview message commands
// are created within the webview context (i.e. inside media/main.js)
}
@@ -1604,6 +1610,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
userInfo,
authToken,
mcpMarketplaceEnabled,
hideTelemetryOptIn,
} = await this.getState()
return {
@@ -1621,8 +1628,11 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
browserSettings,
chatSettings,
isLoggedIn: !!authToken,
advancedSettings: vscode.workspace.getConfiguration("cline"),
vscMachineId: vscode.env.machineId,
userInfo,
mcpMarketplaceEnabled,
hideTelemetryOptIn: hideTelemetryOptIn ?? false,
}
}
@@ -1729,6 +1739,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
previousModeModelInfo,
qwenApiLine,
liteLlmApiKey,
hideTelemetryOptIn,
] = await Promise.all([
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
this.getGlobalState("apiModelId") as Promise<string | undefined>,
@@ -1780,6 +1791,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
this.getGlobalState("previousModeModelInfo") as Promise<ModelInfo | undefined>,
this.getGlobalState("qwenApiLine") as Promise<string | undefined>,
this.getSecret("liteLlmApiKey") as Promise<string | undefined>,
this.getGlobalState("hideTelemetryOptIn") as Promise<boolean | undefined>,
])
let apiProvider: ApiProvider
@@ -1857,6 +1869,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
previousModeModelId,
previousModeModelInfo,
mcpMarketplaceEnabled,
hideTelemetryOptIn,
}
}
+19 -1
View File
@@ -7,6 +7,7 @@ import { Logger } from "./services/logging/Logger"
import { createClineAPI } from "./exports"
import "./utils/path" // necessary to have access to String.prototype.toPosix
import { DIFF_VIEW_URI_SCHEME } from "./integrations/editor/DiffViewProvider"
import posthog from "./services/analytics/PostHogClient"
/*
Built using https://github.com/microsoft/vscode-webview-ui-toolkit
@@ -57,6 +58,22 @@ export function activate(context: vscode.ExtensionContext) {
}),
)
context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration(async (e) => {
if (e.affectsConfiguration("cline")) {
Logger.log("Configuration changed")
await sidebarProvider.postStateToWebview()
const config = vscode.workspace.getConfiguration("cline")
// we use optIn and optOut because we want to keep posthog active for feature flags
if (config.get("enableTelemetry")) {
posthog.optIn()
} else {
posthog.optOut()
}
}
}),
)
const openClineInNewTab = async () => {
Logger.log("Opening Cline in new tab")
// (this example uses webviewProvider activation event which is necessary to deserialize cached webview, but since we use retainContextWhenHidden, we don't need to use that event)
@@ -187,6 +204,7 @@ export function activate(context: vscode.ExtensionContext) {
}
// This method is called when your extension is deactivated
export function deactivate() {
export async function deactivate() {
Logger.log("Cline extension deactivated")
await posthog.shutdown()
}
+42
View File
@@ -0,0 +1,42 @@
import { PostHog } from "posthog-node"
import * as vscode from "vscode"
class PostHogClient {
private static instance: PostHogClient
private client: PostHog
private distinctId: string = vscode.env.machineId
private constructor() {
this.client = new PostHog("phc_5WnLHpYyC30Bsb7VSJ6DzcPXZ34JSF08DJLyM7svZ15", {
host: "https://us.i.posthog.com",
enableExceptionAutocapture: true,
})
}
public static getInstance(): PostHogClient {
if (!PostHogClient.instance) {
PostHogClient.instance = new PostHogClient()
}
return PostHogClient.instance
}
public optIn(): void {
this.client.identify({ distinctId: this.distinctId })
this.client.optIn()
}
public optOut(): void {
this.client.optOut()
}
public capture(event: { event: string; properties?: any }): void {
this.client.capture({ distinctId: this.distinctId, event: event.event, properties: event.properties })
}
public async shutdown(): Promise<void> {
await this.client.shutdown()
}
}
// Export a single instance
export default PostHogClient.getInstance()
+19
View File
@@ -0,0 +1,19 @@
export interface ClineConfiguration {
vsCodeLmModelSelector?: {
vendor?: string
family?: string
}
mcp: {
mode: "full" | "server-use-only" | "off"
}
enableCheckpoints: boolean
enableTelemetry: boolean
}
export const DEFAULT_ADVANCED_SETTINGS: ClineConfiguration = {
mcp: {
mode: "full",
},
enableCheckpoints: true,
enableTelemetry: false,
}
+5
View File
@@ -7,6 +7,8 @@ import { BrowserSettings } from "./BrowserSettings"
import { ChatSettings } from "./ChatSettings"
import { HistoryItem } from "./HistoryItem"
import { McpServer, McpMarketplaceCatalog, McpMarketplaceItem, McpDownloadResponse } from "./mcp"
import { ClineConfiguration } from "./AdvancedSettings"
import { WorkspaceConfiguration } from "vscode"
// webview will hold state
export interface ExtensionMessage {
@@ -75,6 +77,9 @@ export interface ExtensionState {
chatSettings: ChatSettings
isLoggedIn: boolean
platform: Platform
advancedSettings: WorkspaceConfiguration | ClineConfiguration
vscMachineId: string
hideTelemetryOptIn: boolean
userInfo?: {
displayName: string | null
email: string | null
+1
View File
@@ -51,6 +51,7 @@ export interface WebviewMessage {
| "showMcpView"
| "fetchLatestMcpServersFromHub"
| "updateMcpTimeout"
| "toggleTelemetryOptin"
// | "relaunchChromeDebugMode"
text?: string
disabled?: boolean
+66
View File
@@ -15,6 +15,7 @@
"fuse.js": "^7.0.0",
"fzf": "^0.5.2",
"mermaid": "^11.4.1",
"posthog-js": "^1.223.3",
"pretty-bytes": "^6.1.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -37,6 +38,7 @@
"@types/node": "^20.x",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/vscode": "^1.84.0",
"@types/vscode-webview": "^1.57.5",
"jsdom": "^25.0.1",
"vitest": "^2.1.9"
@@ -4285,6 +4287,16 @@
"win32"
]
},
"node_modules/@rrweb/types": {
"version": "2.0.0-alpha.17",
"resolved": "https://registry.npmjs.org/@rrweb/types/-/types-2.0.0-alpha.17.tgz",
"integrity": "sha512-AfDTVUuCyCaIG0lTSqYtrZqJX39ZEYzs4fYKnexhQ+id+kbZIpIJtaut5cto6dWZbB3SEe4fW0o90Po3LvTmfg==",
"license": "MIT",
"peer": true,
"dependencies": {
"rrweb-snapshot": "^2.0.0-alpha.17"
}
},
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -5329,6 +5341,13 @@
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
"license": "MIT"
},
"node_modules/@types/vscode": {
"version": "1.97.0",
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.97.0.tgz",
"integrity": "sha512-ueE73loeOTe7olaVyqP9mrRI54kVPJifUPjblZo9fYcv1CuVLPOEKEkqW0GkqPC454+nCEoigLWnC2Pp7prZ9w==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/vscode-webview": {
"version": "1.57.5",
"resolved": "https://registry.npmjs.org/@types/vscode-webview/-/vscode-webview-1.57.5.tgz",
@@ -10201,6 +10220,12 @@
"bser": "2.1.1"
}
},
"node_modules/fflate": {
"version": "0.4.8",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
"integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==",
"license": "MIT"
},
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -16628,6 +16653,37 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"license": "MIT"
},
"node_modules/posthog-js": {
"version": "1.223.3",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.223.3.tgz",
"integrity": "sha512-ZQTc17M21IzkQmECJa2Xjont4tZrvIn252uGT3sTfmahTqZoW4j+kBj4eOJt9SNR6hOheFNkg7MSiI/rA6FaDA==",
"license": "MIT",
"dependencies": {
"core-js": "^3.38.1",
"fflate": "^0.4.8",
"preact": "^10.19.3",
"web-vitals": "^4.2.0"
},
"peerDependencies": {
"@rrweb/types": "2.0.0-alpha.17"
}
},
"node_modules/posthog-js/node_modules/web-vitals": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz",
"integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==",
"license": "Apache-2.0"
},
"node_modules/preact": {
"version": "10.26.2",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.26.2.tgz",
"integrity": "sha512-0gNmv4qpS9HaN3+40CLBAnKe0ZfyE4ZWo5xKlC1rVrr0ckkEvJvAQqKaHANdFKsGstoxrY4AItZ7kZSGVoVjgg==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -17700,6 +17756,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/rrweb-snapshot": {
"version": "2.0.0-alpha.18",
"resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-2.0.0-alpha.18.tgz",
"integrity": "sha512-hBHZL/NfgQX6wO1D9mpwqFu1NJPpim+moIcKhFEjVTZVRUfCln+LOugRc4teVTCISYHN8Cw5e2iNTWCSm+SkoA==",
"license": "MIT",
"peer": true,
"dependencies": {
"postcss": "^8.4.38"
}
},
"node_modules/rtl-css-js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz",
+2
View File
@@ -10,6 +10,7 @@
"fuse.js": "^7.0.0",
"fzf": "^0.5.2",
"mermaid": "^11.4.1",
"posthog-js": "^1.223.3",
"pretty-bytes": "^6.1.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -60,6 +61,7 @@
"@types/node": "^20.x",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/vscode": "^1.84.0",
"@types/vscode-webview": "^1.57.5",
"jsdom": "^25.0.1",
"vitest": "^2.1.9"
+18 -3
View File
@@ -12,6 +12,14 @@
const rewire = require("rewire")
const defaults = rewire("react-scripts/scripts/build.js")
const config = defaults.__get__("config")
const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin")
const path = require("path")
const fs = require("fs")
const dotenv = require("dotenv")
const webpack = require("webpack")
// Load environment variables from root .env.local
dotenv.config({ path: "../.env" })
/* Modifying Webpack Configuration for 'shared' dir
This section uses Rewire to modify Create React App's webpack configuration without ejecting. Rewire allows us to inject and alter the internal build scripts of CRA at runtime. This allows us to maintain a flexible project structure that keeps shared code outside the webview-ui/src directory, while still adhering to CRA's security model that typically restricts imports to within src/.
@@ -21,12 +29,19 @@ Before, we would just import types from shared dir and specifying include (and a
- Imports from the shared directory must use full paths relative to the src directory, without file extensions.
- Example: import { someFunction } from '../../src/shared/utils/helpers'
*/
const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin")
const path = require("path")
const fs = require("fs")
// Get all files in the shared directory
const sharedDir = path.resolve(__dirname, "..", "..", "src", "shared")
config.plugins.push(
new webpack.DefinePlugin({
"process.env": {
POSTHOG_PROJECT_API_KEY: JSON.stringify(process.env.POSTHOG_PROJECT_API_KEY || ""),
POSTHOG_INSTANCE_ADDRESS: JSON.stringify(process.env.POSTHOG_INSTANCE_ADDRESS || ""),
},
}),
)
function getAllFiles(dir) {
let files = []
fs.readdirSync(dir).forEach((file) => {
+14 -1
View File
@@ -9,14 +9,17 @@ import AccountView from "./components/account/AccountView"
import { ExtensionStateContextProvider, useExtensionState } from "./context/ExtensionStateContext"
import { vscode } from "./utils/vscode"
import McpView from "./components/mcp/McpView"
import posthog from "posthog-js"
const AppContent = () => {
const { didHydrateState, showWelcome, shouldShowAnnouncement } = useExtensionState()
const { didHydrateState, showWelcome, shouldShowAnnouncement, advancedSettings, vscMachineId, hideTelemetryOptIn } =
useExtensionState()
const [showSettings, setShowSettings] = useState(false)
const [showHistory, setShowHistory] = useState(false)
const [showMcp, setShowMcp] = useState(false)
const [showAccount, setShowAccount] = useState(false)
const [showAnnouncement, setShowAnnouncement] = useState(false)
const telemetryEnabled = advancedSettings.enableTelemetry
const handleMessage = useCallback((e: MessageEvent) => {
const message: ExtensionMessage = e.data
@@ -60,6 +63,15 @@ const AppContent = () => {
useEvent("message", handleMessage)
useEffect(() => {
if (telemetryEnabled) {
posthog.identify(vscMachineId)
posthog.opt_in_capturing()
} else {
posthog.opt_out_capturing()
}
}, [telemetryEnabled, vscMachineId])
useEffect(() => {
if (shouldShowAnnouncement) {
setShowAnnouncement(true)
@@ -93,6 +105,7 @@ const AppContent = () => {
hideAnnouncement={() => {
setShowAnnouncement(false)
}}
hideTelemetryOptIn={hideTelemetryOptIn}
/>
</>
)}
+5 -2
View File
@@ -1,4 +1,4 @@
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import debounce from "debounce"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useDeepCompareEffect, useEvent, useMount } from "react-use"
@@ -26,17 +26,19 @@ import BrowserSessionRow from "./BrowserSessionRow"
import ChatRow from "./ChatRow"
import ChatTextArea from "./ChatTextArea"
import TaskHeader from "./TaskHeader"
import TelemetryOptin from "./TelemetryOptin"
interface ChatViewProps {
isHidden: boolean
showAnnouncement: boolean
hideAnnouncement: () => void
showHistoryView: () => void
hideTelemetryOptIn: boolean
}
export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryView }: ChatViewProps) => {
const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryView, hideTelemetryOptIn }: ChatViewProps) => {
const { version, clineMessages: messages, taskHistory, apiConfiguration } = useExtensionState()
//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined
@@ -790,6 +792,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
paddingBottom: "10px",
}}>
{showAnnouncement && <Announcement version={version} hideAnnouncement={hideAnnouncement} />}
{!hideTelemetryOptIn && <TelemetryOptin />}
<div style={{ padding: "0 20px", flexShrink: 0 }}>
<h2>What can I do for you?</h2>
<p>
@@ -0,0 +1,86 @@
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { memo, useState, useEffect } from "react"
import { getAsVar, VSC_INACTIVE_SELECTION_BACKGROUND } from "../../utils/vscStyles"
import { vscode } from "../../utils/vscode"
const boxStyles = {
backgroundColor: getAsVar(VSC_INACTIVE_SELECTION_BACKGROUND),
borderRadius: "3px",
padding: "12px 16px",
margin: "5px 15px 5px 15px",
position: "relative" as const,
flexShrink: 0,
minHeight: "120px",
display: "flex",
flexDirection: "column" as const,
justifyContent: "center" as const,
}
const TelemetryOptin = () => {
const [showThankYou, setShowThankYou] = useState(false)
const [isVisible, setIsVisible] = useState(true)
useEffect(() => {
if (showThankYou) {
const fadeTimer = setTimeout(() => {
setIsVisible(false)
}, 1500)
const closeTimer = setTimeout(() => {
vscode.postMessage({ type: "toggleTelemetryOptin", bool: true })
}, 2000)
return () => {
clearTimeout(fadeTimer)
clearTimeout(closeTimer)
}
}
}, [showThankYou])
const handleOptIn = () => {
setShowThankYou(true)
}
const handleCancel = () => {
vscode.postMessage({ type: "toggleTelemetryOptin", bool: false })
}
if (showThankYou) {
return (
<div
style={{
...boxStyles,
opacity: isVisible ? 1 : 0,
transition: "opacity 0.5s ease-out",
textAlign: "center",
}}>
<h3 style={{ margin: "0" }}>Thank you for helping improve Cline!</h3>
</div>
)
}
return (
<div style={boxStyles}>
<h3 style={{ margin: "0 0 8px" }}>Help Improve Cline</h3>
<p style={{ margin: "0 0 12px" }}>
Would you like to help make Cline better by sending anonymous error reports and usage data? No personal or project
information will be collected. You can change this setting anytime in{" "}
<VSCodeLink onClick={() => vscode.postMessage({ type: "openExtensionSettings" })}>VS Code preferences</VSCodeLink>
.{" "}
<VSCodeLink href="https://github.com/cline/cline/blob/main/docs/PRIVACY.md" style={{ display: "inline" }}>
Learn more
</VSCodeLink>
</p>
<div style={{ display: "flex", gap: "8px" }}>
<VSCodeButton appearance="primary" onClick={handleOptIn}>
Opt In
</VSCodeButton>
<VSCodeButton appearance="secondary" onClick={handleCancel}>
Cancel
</VSCodeButton>
</div>
</div>
)
}
export default memo(TelemetryOptin)
@@ -9,6 +9,7 @@ import { convertTextMateToHljs } from "../utils/textMateToHljs"
import { vscode } from "../utils/vscode"
import { DEFAULT_BROWSER_SETTINGS } from "../../../src/shared/BrowserSettings"
import { DEFAULT_CHAT_SETTINGS } from "../../../src/shared/ChatSettings"
import { DEFAULT_ADVANCED_SETTINGS } from "../../../src/shared/AdvancedSettings"
interface ExtensionStateContextType extends ExtensionState {
didHydrateState: boolean
@@ -39,6 +40,9 @@ export const ExtensionStateContextProvider: React.FC<{
chatSettings: DEFAULT_CHAT_SETTINGS,
isLoggedIn: false,
platform: DEFAULT_PLATFORM,
vscMachineId: "",
advancedSettings: DEFAULT_ADVANCED_SETTINGS,
hideTelemetryOptIn: false,
})
const [didHydrateState, setDidHydrateState] = useState(false)
const [showWelcome, setShowWelcome] = useState(false)
+7 -1
View File
@@ -4,11 +4,17 @@ import "./index.css"
import App from "./App"
import reportWebVitals from "./reportWebVitals"
import "../../node_modules/@vscode/codicons/dist/codicon.css"
import { PostHogProvider } from "posthog-js/react"
const apiKey = process.env.POSTHOG_PROJECT_API_KEY || ""
const instanceAddress = process.env.POSTHOG_INSTANCE_ADDRESS || ""
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
root.render(
<React.StrictMode>
<App />
<PostHogProvider apiKey={apiKey} options={{ api_host: instanceAddress, capture_pageview: false }}>
<App />
</PostHogProvider>
</React.StrictMode>,
)