diff --git a/.plugin/plugin.json b/.plugin/plugin.json index a1cbe91..7f6f894 100644 --- a/.plugin/plugin.json +++ b/.plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "vercel-plugin", - "version": "0.32.1", + "version": "0.32.2", "description": "Comprehensive Vercel ecosystem plugin — relational knowledge graph, skills for every major product, specialized agents, and Vercel conventions. Turns any AI agent into a Vercel expert.", "author": { "name": "Vercel Labs", diff --git a/README.md b/README.md index 83913af..a518c87 100644 --- a/README.md +++ b/README.md @@ -113,14 +113,14 @@ After installing, skills and context are injected automatically. You can also in The plugin has two separate telemetry controls: -- `~/.claude/vercel-plugin-telemetry-preference` controls raw content telemetry only. +- `~/.claude/vercel-plugin-telemetry-preference` controls prompt text only. - `VERCEL_PLUGIN_TELEMETRY=off` disables all telemetry. Behavior: -- `echo 'enabled' > ~/.claude/vercel-plugin-telemetry-preference` keeps default base telemetry on and also allows raw content telemetry for prompt text and full bash commands. -- `echo 'disabled' > ~/.claude/vercel-plugin-telemetry-preference` keeps prompt text and full bash commands off, but base telemetry remains on by default. -- `VERCEL_PLUGIN_TELEMETRY=off` disables all telemetry, including prompt text, full bash command telemetry, session metadata, tool names, and skill-injection telemetry. +- `echo 'enabled' > ~/.claude/vercel-plugin-telemetry-preference` keeps default base telemetry on and also allows prompt text telemetry. +- `echo 'disabled' > ~/.claude/vercel-plugin-telemetry-preference` keeps prompt text off, but base telemetry remains on by default. +- `VERCEL_PLUGIN_TELEMETRY=off` disables all telemetry, including prompt text, session metadata, tool names, and skill-injection telemetry. Where to set `VERCEL_PLUGIN_TELEMETRY=off`: diff --git a/hooks/posttooluse-telemetry.mjs b/hooks/posttooluse-telemetry.mjs index 8b4f6cc..75c7a13 100755 --- a/hooks/posttooluse-telemetry.mjs +++ b/hooks/posttooluse-telemetry.mjs @@ -2,7 +2,6 @@ // hooks/src/posttooluse-telemetry.mts import { readFileSync } from "fs"; -import { trackContentEvents } from "./telemetry.mjs"; function parseStdin() { try { const raw = readFileSync(0, "utf-8").trim(); @@ -13,27 +12,7 @@ function parseStdin() { } } async function main() { - const input = parseStdin(); - if (!input) { - process.stdout.write("{}"); - process.exit(0); - } - const toolName = input.tool_name || ""; - const toolInput = input.tool_input || {}; - const sessionId = input.session_id || input.conversation_id || ""; - if (!sessionId) { - process.stdout.write("{}"); - process.exit(0); - } - const entries = []; - if (toolName === "Bash") { - entries.push( - { key: "bash:command", value: toolInput.command || "" } - ); - } - if (entries.length > 0) { - await trackContentEvents(sessionId, entries); - } + parseStdin(); process.stdout.write("{}"); process.exit(0); } diff --git a/hooks/pretooluse-skill-inject.mjs b/hooks/pretooluse-skill-inject.mjs index 6d0996e..c9ebf86 100644 --- a/hooks/pretooluse-skill-inject.mjs +++ b/hooks/pretooluse-skill-inject.mjs @@ -33,7 +33,7 @@ import { } from "./patterns.mjs"; import { resolveVercelJsonSkills, isVercelJsonPath, VERCEL_JSON_SKILLS } from "./vercel-config.mjs"; import { createLogger, logDecision } from "./logger.mjs"; -import { trackBaseEvents, trackContentEvents } from "./telemetry.mjs"; +import { trackBaseEvents } from "./telemetry.mjs"; import { selectManagedContextChunk } from "./vercel-context.mjs"; var MAX_SKILLS = 3; var DEFAULT_INJECTION_BUDGET_BYTES = 18e3; @@ -608,13 +608,6 @@ function run() { const toolEntries = [ { key: "tool_call:tool_name", value: toolName } ]; - if (toolName === "Bash") { - trackContentEvents(sessionId, [ - { key: "tool_call:target", value: toolTarget }, - { key: "tool_call:command", value: toolInput.command || "" } - ]).catch(() => { - }); - } trackBaseEvents(sessionId, toolEntries).catch(() => { }); } diff --git a/hooks/setup-telemetry.mjs b/hooks/setup-telemetry.mjs index 82fbe8a..1ba166d 100755 --- a/hooks/setup-telemetry.mjs +++ b/hooks/setup-telemetry.mjs @@ -10,7 +10,7 @@ function main() { const telemetryOverride = getTelemetryOverride(); if (telemetryOverride === "off") { process.stdout.write( - "Telemetry is fully disabled via VERCEL_PLUGIN_TELEMETRY=off.\nRemove or change that env var before changing the content-sharing preference.\n" + "Telemetry is fully disabled via VERCEL_PLUGIN_TELEMETRY=off.\nRemove or change that env var before changing the prompt-sharing preference.\n" ); process.exit(0); } @@ -21,8 +21,8 @@ function main() { } if (existing === "enabled" || existing === "disabled" || existing === "asked") { process.stdout.write( - `Content telemetry is currently ${existing}. This setting controls prompt text and full bash command sharing only. -To change content sharing, edit ~/.claude/vercel-plugin-telemetry-preference. To disable all telemetry, set VERCEL_PLUGIN_TELEMETRY=off in the environment that launches your agent. + `Prompt telemetry is currently ${existing}. This setting controls prompt text sharing only. +To change prompt sharing, edit ~/.claude/vercel-plugin-telemetry-preference. To disable all telemetry, set VERCEL_PLUGIN_TELEMETRY=off in the environment that launches your agent. ` ); process.exit(0); @@ -30,7 +30,7 @@ To change content sharing, edit ~/.claude/vercel-plugin-telemetry-preference. To process.stdout.write( [ "The Vercel plugin collects anonymous usage data such as skill injection patterns and tools used by default.", - "Ask the user if they would also like to share raw content telemetry, including prompt text and full bash commands, to help improve skill matching and command understanding. This choice only controls content telemetry.", + "Ask the user if they would also like to share their prompt text to help improve skill matching. This choice only controls prompt telemetry.", "To disable all telemetry, set VERCEL_PLUGIN_TELEMETRY=off in the environment that launches your agent.", "If they agree, run `echo 'enabled' > ~/.claude/vercel-plugin-telemetry-preference`.", "If they decline, run `echo 'disabled' > ~/.claude/vercel-plugin-telemetry-preference`.", diff --git a/hooks/src/posttooluse-telemetry.mts b/hooks/src/posttooluse-telemetry.mts index d6ce2bf..b202acc 100644 --- a/hooks/src/posttooluse-telemetry.mts +++ b/hooks/src/posttooluse-telemetry.mts @@ -1,7 +1,6 @@ #!/usr/bin/env node import { readFileSync } from "node:fs"; -import { trackContentEvents } from "./telemetry.mjs"; function parseStdin(): Record | null { try { @@ -14,58 +13,7 @@ function parseStdin(): Record | null { } async function main(): Promise { - // Content telemetry — opt-in only unless VERCEL_PLUGIN_TELEMETRY=off disables all telemetry - - const input = parseStdin(); - if (!input) { - process.stdout.write("{}"); - process.exit(0); - } - - const toolName = (input.tool_name as string) || ""; - const toolInput = (input.tool_input as Record) || {}; - const sessionId = (input.session_id as string) || (input.conversation_id as string) || ""; - - if (!sessionId) { - process.stdout.write("{}"); - process.exit(0); - } - - const entries: Array<{ key: string; value: string }> = []; - - // Code change tracking (Edit/Write) disabled pending legal approval. - // TODO: Re-enable once legal signs off on collecting code content. - // if (toolName === "Edit") { - // const filePath = (toolInput.file_path as string) || ""; - // const cwdCandidate = input.cwd ?? input.working_directory; - // const cwd = typeof cwdCandidate === "string" && cwdCandidate.trim() !== "" ? cwdCandidate : null; - // const resolvedPath = cwd ? resolve(cwd, filePath) : filePath; - // entries.push( - // { key: "code_change:tool", value: "Edit" }, - // { key: "code_change:file_path", value: resolvedPath }, - // { key: "code_change:old_string", value: (toolInput.old_string as string) || "" }, - // { key: "code_change:new_string", value: (toolInput.new_string as string) || "" }, - // ); - // } else if (toolName === "Write") { - // const filePath = (toolInput.file_path as string) || ""; - // const cwdCandidate = input.cwd ?? input.working_directory; - // const cwd = typeof cwdCandidate === "string" && cwdCandidate.trim() !== "" ? cwdCandidate : null; - // const resolvedPath = cwd ? resolve(cwd, filePath) : filePath; - // entries.push( - // { key: "code_change:tool", value: "Write" }, - // { key: "code_change:file_path", value: resolvedPath }, - // { key: "code_change:content", value: (toolInput.content as string) || "" }, - // ); - // } else - if (toolName === "Bash") { - entries.push( - { key: "bash:command", value: (toolInput.command as string) || "" }, - ); - } - - if (entries.length > 0) { - await trackContentEvents(sessionId, entries); - } + parseStdin(); process.stdout.write("{}"); process.exit(0); diff --git a/hooks/src/pretooluse-skill-inject.mts b/hooks/src/pretooluse-skill-inject.mts index 03e975c..355199c 100644 --- a/hooks/src/pretooluse-skill-inject.mts +++ b/hooks/src/pretooluse-skill-inject.mts @@ -58,7 +58,7 @@ import { resolveVercelJsonSkills, isVercelJsonPath, VERCEL_JSON_SKILLS } from ". import type { VercelJsonRouting } from "./vercel-config.mjs"; import { createLogger, logDecision } from "./logger.mjs"; import type { Logger } from "./logger.mjs"; -import { trackBaseEvents, trackContentEvents } from "./telemetry.mjs"; +import { trackBaseEvents } from "./telemetry.mjs"; import { selectManagedContextChunk } from "./vercel-context.mjs"; const MAX_SKILLS = 3; @@ -968,12 +968,6 @@ function run(): string { const toolEntries: Array<{ key: string; value: string }> = [ { key: "tool_call:tool_name", value: toolName }, ]; - if (toolName === "Bash") { - trackContentEvents(sessionId, [ - { key: "tool_call:target", value: toolTarget }, - { key: "tool_call:command", value: (toolInput.command as string) || "" }, - ]).catch(() => {}); - } trackBaseEvents(sessionId, toolEntries).catch(() => {}); } diff --git a/hooks/src/setup-telemetry.mts b/hooks/src/setup-telemetry.mts index e67d51f..033fd7e 100644 --- a/hooks/src/setup-telemetry.mts +++ b/hooks/src/setup-telemetry.mts @@ -11,7 +11,7 @@ function main(): void { const telemetryOverride = getTelemetryOverride(); if (telemetryOverride === "off") { process.stdout.write( - "Telemetry is fully disabled via VERCEL_PLUGIN_TELEMETRY=off.\nRemove or change that env var before changing the content-sharing preference.\n", + "Telemetry is fully disabled via VERCEL_PLUGIN_TELEMETRY=off.\nRemove or change that env var before changing the prompt-sharing preference.\n", ); process.exit(0); } @@ -25,7 +25,7 @@ function main(): void { if (existing === "enabled" || existing === "disabled" || existing === "asked") { process.stdout.write( - `Content telemetry is currently ${existing}. This setting controls prompt text and full bash command sharing only.\nTo change content sharing, edit ~/.claude/vercel-plugin-telemetry-preference. To disable all telemetry, set VERCEL_PLUGIN_TELEMETRY=off in the environment that launches your agent.\n`, + `Prompt telemetry is currently ${existing}. This setting controls prompt text sharing only.\nTo change prompt sharing, edit ~/.claude/vercel-plugin-telemetry-preference. To disable all telemetry, set VERCEL_PLUGIN_TELEMETRY=off in the environment that launches your agent.\n`, ); process.exit(0); } @@ -33,7 +33,7 @@ function main(): void { process.stdout.write( [ "The Vercel plugin collects anonymous usage data such as skill injection patterns and tools used by default.", - "Ask the user if they would also like to share raw content telemetry, including prompt text and full bash commands, to help improve skill matching and command understanding. This choice only controls content telemetry.", + "Ask the user if they would also like to share their prompt text to help improve skill matching. This choice only controls prompt telemetry.", "To disable all telemetry, set VERCEL_PLUGIN_TELEMETRY=off in the environment that launches your agent.", "If they agree, run `echo 'enabled' > ~/.claude/vercel-plugin-telemetry-preference`.", "If they decline, run `echo 'disabled' > ~/.claude/vercel-plugin-telemetry-preference`.", diff --git a/hooks/src/telemetry.mts b/hooks/src/telemetry.mts index 4978d7a..50e375b 100644 --- a/hooks/src/telemetry.mts +++ b/hooks/src/telemetry.mts @@ -82,7 +82,7 @@ export function getOrCreateDeviceId(): string { /** * Content-level telemetry (opt-in): requires explicit user consent. - * Gates collection of raw content such as prompt:text and bash:command. + * Currently gates prompt:text only. */ export function getTelemetryOverride(env: NodeJS.ProcessEnv = process.env): "off" | null { const value = env.VERCEL_PLUGIN_TELEMETRY?.trim().toLowerCase(); @@ -157,7 +157,7 @@ export async function trackBaseEvents( } // --------------------------------------------------------------------------- -// Opt-in telemetry (raw content) +// Opt-in telemetry (raw prompt content) // --------------------------------------------------------------------------- export async function trackContentEvent(sessionId: string, key: string, value: string): Promise { diff --git a/hooks/src/user-prompt-submit-telemetry.mts b/hooks/src/user-prompt-submit-telemetry.mts index 34ab2f1..50944aa 100644 --- a/hooks/src/user-prompt-submit-telemetry.mts +++ b/hooks/src/user-prompt-submit-telemetry.mts @@ -1,21 +1,21 @@ #!/usr/bin/env node /** - * UserPromptSubmit hook: content telemetry opt-in + prompt text tracking. + * UserPromptSubmit hook: prompt telemetry opt-in + prompt text tracking. * * Fires on every user message. Two responsibilities: * * 1. Track prompt:text telemetry (awaited) for every prompt >= 10 chars - * when content telemetry is enabled. This runs independently of skill + * when prompt telemetry is enabled. This runs independently of skill * matching so prompts are never silently dropped. * * 2. On the first message of a session where the user hasn't recorded a - * content telemetry preference, return additionalContext asking the model + * prompt telemetry preference, return additionalContext asking the model * to prompt the user for opt-in. Writes "asked" immediately so the user * is never re-prompted. session-end-cleanup converts "asked" → "disabled". * * Note: Base telemetry is enabled by default, but users can disable all * telemetry with VERCEL_PLUGIN_TELEMETRY=off. This hook only gates prompt - * text and full bash command collection when telemetry is otherwise enabled. + * text collection when telemetry is otherwise enabled. * * Input: JSON on stdin with { session_id, prompt } * Output: JSON on stdout with { hookSpecificOutput: { hookEventName, additionalContext } } or {} @@ -106,13 +106,13 @@ async function main(): Promise { const message = [ "After responding to the user's message, use the AskUserQuestion tool to ask about telemetry.", "Use this exact question configuration:", - '- question: "The Vercel plugin collects anonymous usage data such as skill injection patterns and tools used by default. Would you like to also share raw content telemetry, including your prompt text and full bash commands, to help improve skill matching and command understanding? This choice only controls content telemetry."', + '- question: "The Vercel plugin collects anonymous usage data such as skill injection patterns and tools used by default. Would you like to also share your prompt text to help improve skill matching? This choice only controls prompt telemetry."', '- header: "Telemetry"', - '- options: [{"label": "Share content", "description": "Share prompt text and full bash commands in addition to default anonymous usage data"}, {"label": "No thanks", "description": "Keep prompt text and full bash commands private. This only disables content telemetry"}]', + '- options: [{"label": "Share prompts", "description": "Share prompt text in addition to default anonymous usage data"}, {"label": "No thanks", "description": "Keep prompt text private. This only disables prompt telemetry"}]', "- multiSelect: false", "", "After the user responds:", - "- If they chose \"Share content\", run: `echo 'enabled' > ~/.claude/vercel-plugin-telemetry-preference`", + "- If they chose \"Share prompts\", run: `echo 'enabled' > ~/.claude/vercel-plugin-telemetry-preference`", "- If they chose \"No thanks\" or anything else, run: `echo 'disabled' > ~/.claude/vercel-plugin-telemetry-preference`", ].join("\n"); diff --git a/hooks/user-prompt-submit-telemetry.mjs b/hooks/user-prompt-submit-telemetry.mjs index a0077ca..351c1c0 100755 --- a/hooks/user-prompt-submit-telemetry.mjs +++ b/hooks/user-prompt-submit-telemetry.mjs @@ -67,13 +67,13 @@ async function main() { const message = [ "After responding to the user's message, use the AskUserQuestion tool to ask about telemetry.", "Use this exact question configuration:", - '- question: "The Vercel plugin collects anonymous usage data such as skill injection patterns and tools used by default. Would you like to also share raw content telemetry, including your prompt text and full bash commands, to help improve skill matching and command understanding? This choice only controls content telemetry."', + '- question: "The Vercel plugin collects anonymous usage data such as skill injection patterns and tools used by default. Would you like to also share your prompt text to help improve skill matching? This choice only controls prompt telemetry."', '- header: "Telemetry"', - '- options: [{"label": "Share content", "description": "Share prompt text and full bash commands in addition to default anonymous usage data"}, {"label": "No thanks", "description": "Keep prompt text and full bash commands private. This only disables content telemetry"}]', + '- options: [{"label": "Share prompts", "description": "Share prompt text in addition to default anonymous usage data"}, {"label": "No thanks", "description": "Keep prompt text private. This only disables prompt telemetry"}]', "- multiSelect: false", "", "After the user responds:", - "- If they chose \"Share content\", run: `echo 'enabled' > ~/.claude/vercel-plugin-telemetry-preference`", + "- If they chose \"Share prompts\", run: `echo 'enabled' > ~/.claude/vercel-plugin-telemetry-preference`", "- If they chose \"No thanks\" or anything else, run: `echo 'disabled' > ~/.claude/vercel-plugin-telemetry-preference`" ].join("\n"); const output = { diff --git a/package.json b/package.json index b38f45b..895d46d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vercel-plugin", - "version": "0.32.1", + "version": "0.32.2", "private": true, "bin": { "vercel-plugin": "src/cli/index.ts" diff --git a/tests/telemetry.test.ts b/tests/telemetry.test.ts index 51da221..c904c9c 100644 --- a/tests/telemetry.test.ts +++ b/tests/telemetry.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; -import { existsSync, mkdtempSync, rmSync } from "node:fs"; +import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; @@ -134,4 +134,12 @@ describe("telemetry controls", () => { expect(result.stdout).toBe("{}"); expect(existsSync(prefPath)).toBe(false); }); + + test("compiled hooks do not emit bash command telemetry keys", () => { + const pretoolHook = readFileSync(join(ROOT, "hooks", "pretooluse-skill-inject.mjs"), "utf-8"); + const posttoolHook = readFileSync(join(ROOT, "hooks", "posttooluse-telemetry.mjs"), "utf-8"); + + expect(pretoolHook.includes("tool_call:command")).toBe(false); + expect(posttoolHook.includes("bash:command")).toBe(false); + }); });