Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:

Expand Down
23 changes: 1 addition & 22 deletions hooks/posttooluse-telemetry.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
}
Expand Down
9 changes: 1 addition & 8 deletions hooks/pretooluse-skill-inject.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(() => {
});
}
Expand Down
8 changes: 4 additions & 4 deletions hooks/setup-telemetry.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -21,16 +21,16 @@ 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);
}
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`.",
Expand Down
54 changes: 1 addition & 53 deletions hooks/src/posttooluse-telemetry.mts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#!/usr/bin/env node

import { readFileSync } from "node:fs";
import { trackContentEvents } from "./telemetry.mjs";

function parseStdin(): Record<string, unknown> | null {
try {
Expand All @@ -14,58 +13,7 @@ function parseStdin(): Record<string, unknown> | null {
}

async function main(): Promise<void> {
// 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<string, unknown>) || {};
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);
Expand Down
8 changes: 1 addition & 7 deletions hooks/src/pretooluse-skill-inject.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(() => {});
}

Expand Down
6 changes: 3 additions & 3 deletions hooks/src/setup-telemetry.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -25,15 +25,15 @@ 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);
}

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`.",
Expand Down
4 changes: 2 additions & 2 deletions hooks/src/telemetry.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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<void> {
Expand Down
14 changes: 7 additions & 7 deletions hooks/src/user-prompt-submit-telemetry.mts
Original file line number Diff line number Diff line change
@@ -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 {}
Expand Down Expand Up @@ -106,13 +106,13 @@ async function main(): Promise<void> {
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");

Expand Down
6 changes: 3 additions & 3 deletions hooks/user-prompt-submit-telemetry.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vercel-plugin",
"version": "0.32.1",
"version": "0.32.2",
"private": true,
"bin": {
"vercel-plugin": "src/cli/index.ts"
Expand Down
10 changes: 9 additions & 1 deletion tests/telemetry.test.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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);
});
});
Loading