diff --git a/package.json b/package.json index 502003f..da7c8cc 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,17 @@ } ] }, - "commands": [] + "commands": [], + "configuration": { + "title": "Overwrite", + "properties": { + "overwrite.telemetry.enabled": { + "type": "boolean", + "default": true, + "description": "Enable anonymous usage telemetry to help improve the extension. All data is anonymized and no file contents or paths are ever collected." + } + } + } }, "scripts": { "vscode:package": "pnpm run package && pnpm vsce package --no-dependencies", diff --git a/src/services/telemetry.ts b/src/services/telemetry.ts index 41219f5..e998978 100644 --- a/src/services/telemetry.ts +++ b/src/services/telemetry.ts @@ -1,6 +1,4 @@ import crypto from 'node:crypto' -import fs from 'node:fs' -import path from 'node:path' import { PostHog } from 'posthog-node' import * as vscode from 'vscode' @@ -33,16 +31,6 @@ class TelemetryService { this.context = context this.startedAt = Date.now() - if (!this.isEnabled()) return - - const apiKey = this.getApiKey(context) - if (!apiKey) { - console.warn('[telemetry] POSTHOG_API_KEY not set; telemetry disabled') - return - } - - this.client = new PostHog(apiKey, { host: 'https://us.i.posthog.com' }) - // Persist anonymous id; never use VS Code machineId directly this.distinctId = context.globalState.get('telemetryDistinctId') if (!this.distinctId) { @@ -52,10 +40,33 @@ class TelemetryService { this.sessionId = crypto.randomUUID() + // Initialize client if telemetry is enabled + this.initializeClient() + + // Listen for configuration changes + context.subscriptions.push( + vscode.workspace.onDidChangeConfiguration((e) => { + if ( + e.affectsConfiguration('overwrite.telemetry.enabled') || + e.affectsConfiguration('telemetry.telemetryLevel') + ) { + this.handleConfigurationChange() + } + }), + ) + } + + private initializeClient() { + if (!this.isEnabled()) return + if (this.client) return // Already initialized + + const apiKey = 'phc_LBityQJ7WJhm3iikesHTPvcLGqwUrSoJqofTKJo35rg' + this.client = new PostHog(apiKey, { host: 'https://us.i.posthog.com' }) + // Fire activation event with minimal props this.capture('extension_activated', { activation_time_ms: Date.now() - this.startedAt, - ext_version: context.extension.packageJSON?.version, + ext_version: this.context?.extension.packageJSON?.version, vscode_version: vscode.version, os: process.platform, node_version: process.versions.node, @@ -63,31 +74,34 @@ class TelemetryService { }) } - isEnabled(): boolean { - return true - } - - private getApiKey(context: vscode.ExtensionContext): string | undefined { - if (process.env.POSTHOG_API_KEY) return process.env.POSTHOG_API_KEY - - // Fallback: try to read a .env at the extension root once at startup - try { - const root = context.extensionUri.fsPath - const envPath = path.join(root, '.env') - if (!fs.existsSync(envPath)) return undefined - const content = fs.readFileSync(envPath, 'utf8') - for (const line of content.split(/\r?\n/)) { - const m = line.match(/^\s*POSTHOG_API_KEY\s*=\s*(.*)\s*$/) - if (m) { - // Strip optional quotes - const raw = m[1]?.trim().replace(/^['"]|['"]$/g, '') - if (raw) return raw - } + private handleConfigurationChange() { + if (this.isEnabled()) { + // Telemetry was enabled, initialize if not already done + this.initializeClient() + } else { + // Telemetry was disabled, shutdown client + if (this.client) { + void this.client.shutdown() + this.client = undefined } - } catch { - // ignore } - return undefined + } + + isEnabled(): boolean { + // Check VS Code global telemetry setting + const globalTelemetryLevel = vscode.workspace + .getConfiguration('telemetry') + .get('telemetryLevel') + if (globalTelemetryLevel === 'off') return false + + // Check extension-specific setting + const extensionTelemetryEnabled = vscode.workspace + .getConfiguration('overwrite.telemetry') + .get('enabled', true) // default to true + + console.log(`Overwrite telemetry enabled: ${extensionTelemetryEnabled}`) + + return extensionTelemetryEnabled } capture(event: TelemetryEvent, properties?: Record) { diff --git a/src/webview-ui/pnpm-lock.yaml b/src/webview-ui/pnpm-lock.yaml index 724c288..02ace25 100644 --- a/src/webview-ui/pnpm-lock.yaml +++ b/src/webview-ui/pnpm-lock.yaml @@ -8,9 +8,6 @@ importers: .: dependencies: - '@radix-ui/react-accordion': - specifier: ^1.2.12 - version: 1.2.12(@types/react-dom@19.1.3(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@vscode-elements/elements': specifier: ^2.3.0 version: 2.3.0 @@ -392,146 +389,6 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@radix-ui/primitive@1.1.3': - resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} - - '@radix-ui/react-accordion@1.2.12': - resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-collapsible@1.1.12': - resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-collection@1.1.7': - resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-compose-refs@1.1.2': - resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-context@1.1.2': - resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-direction@1.1.1': - resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-id@1.1.1': - resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-presence@1.1.5': - resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-primitive@2.1.3': - resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-slot@1.2.3': - resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-controllable-state@1.2.2': - resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-effect-event@0.0.2': - resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-layout-effect@1.1.1': - resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@rollup/rollup-android-arm-eabi@4.40.1': resolution: {integrity: sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==} cpu: [arm] @@ -2367,125 +2224,6 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - '@radix-ui/primitive@1.1.3': {} - - '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.1.3(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.1.3(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.3(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-direction': 1.1.1(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.3(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - optionalDependencies: - '@types/react': 19.1.2 - '@types/react-dom': 19.1.3(@types/react@19.1.2) - - '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.1.3(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.3(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.3(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - optionalDependencies: - '@types/react': 19.1.2 - '@types/react-dom': 19.1.3(@types/react@19.1.2) - - '@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.3(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.3(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - optionalDependencies: - '@types/react': 19.1.2 - '@types/react-dom': 19.1.3(@types/react@19.1.2) - - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.2)(react@19.1.0)': - dependencies: - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - - '@radix-ui/react-context@1.1.2(@types/react@19.1.2)(react@19.1.0)': - dependencies: - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - - '@radix-ui/react-direction@1.1.1(@types/react@19.1.2)(react@19.1.0)': - dependencies: - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - - '@radix-ui/react-id@1.1.1(@types/react@19.1.2)(react@19.1.0)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - - '@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.3(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - optionalDependencies: - '@types/react': 19.1.2 - '@types/react-dom': 19.1.3(@types/react@19.1.2) - - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.3(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - optionalDependencies: - '@types/react': 19.1.2 - '@types/react-dom': 19.1.3(@types/react@19.1.2) - - '@radix-ui/react-slot@1.2.3(@types/react@19.1.2)(react@19.1.0)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.2)(react@19.1.0)': - dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.2)(react@19.1.0)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.2)(react@19.1.0)': - dependencies: - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - '@rollup/rollup-android-arm-eabi@4.40.1': optional: true