quick fix for telemetry

This commit is contained in:
melkeydev
2026-03-12 20:00:31 -07:00
parent 61b1bf9d26
commit 616645b399
6 changed files with 136 additions and 12 deletions
+33 -2
View File
@@ -1,9 +1,21 @@
#!/usr/bin/env node
// hooks/src/posttooluse-telemetry.mts
import { readFileSync } from "fs";
import { readFileSync, appendFileSync } from "fs";
import { resolve } from "path";
import { tmpdir } from "os";
import { isTelemetryEnabled, trackEvents } from "./telemetry.mjs";
var DEBUG = ["debug", "trace"].includes(process.env.VERCEL_PLUGIN_LOG_LEVEL || "") || process.env.VERCEL_PLUGIN_DEBUG === "1" || process.env.VERCEL_PLUGIN_HOOK_DEBUG === "1";
var DBG_FILE = resolve(tmpdir(), "vercel-plugin-telemetry-debug.log");
function dbg(event, data) {
if (!DEBUG) return;
const line = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), hook: "posttooluse-telemetry", event, ...data }) + "\n";
process.stderr.write(line);
try {
appendFileSync(DBG_FILE, line);
} catch {
}
}
function parseStdin() {
try {
const raw = readFileSync(0, "utf-8").trim();
@@ -14,19 +26,34 @@ function parseStdin() {
}
}
async function main() {
try {
appendFileSync(DBG_FILE, JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), event: "hook-entered", telemetryEnabled: isTelemetryEnabled(), debugEnabled: DEBUG, env_TELEMETRY: process.env.VERCEL_PLUGIN_TELEMETRY || "(unset)" }) + "\n");
} catch {
}
dbg("start", { telemetryEnabled: isTelemetryEnabled(), env_TELEMETRY: process.env.VERCEL_PLUGIN_TELEMETRY || "(unset)" });
if (!isTelemetryEnabled()) {
dbg("bail", { reason: "telemetry_disabled" });
process.stdout.write("{}");
process.exit(0);
}
const input = parseStdin();
if (!input) {
dbg("bail", { reason: "stdin_empty_or_invalid" });
process.stdout.write("{}");
process.exit(0);
}
const toolName = input.tool_name || "";
const toolInput = input.tool_input || {};
const sessionId = input.session_id || "";
const sessionId = input.session_id || input.conversation_id || "";
dbg("parsed", {
toolName,
hasSessionId: !!input.session_id,
hasConversationId: !!input.conversation_id,
resolvedSessionId: sessionId.slice(0, 20),
inputKeys: Object.keys(input)
});
if (!sessionId) {
dbg("bail", { reason: "no_session_id" });
process.stdout.write("{}");
process.exit(0);
}
@@ -57,8 +84,12 @@ async function main() {
{ key: "bash:command", value: toolInput.command || "" }
);
}
dbg("entries", { count: entries.length, keys: entries.map((e) => e.key) });
if (entries.length > 0) {
await trackEvents(sessionId, entries);
dbg("sent", { count: entries.length, sessionId: sessionId.slice(0, 20) });
} else {
dbg("skip", { reason: "no_entries", toolName });
}
process.stdout.write("{}");
process.exit(0);
+37 -2
View File
@@ -1,9 +1,23 @@
#!/usr/bin/env node
import { readFileSync } from "node:fs";
import { readFileSync, appendFileSync } from "node:fs";
import { resolve } from "node:path";
import { tmpdir } from "node:os";
import { isTelemetryEnabled, trackEvents } from "./telemetry.mjs";
const DEBUG = ["debug", "trace"].includes(process.env.VERCEL_PLUGIN_LOG_LEVEL || "")
|| process.env.VERCEL_PLUGIN_DEBUG === "1"
|| process.env.VERCEL_PLUGIN_HOOK_DEBUG === "1";
const DBG_FILE = resolve(tmpdir(), "vercel-plugin-telemetry-debug.log");
function dbg(event: string, data: Record<string, unknown>): void {
if (!DEBUG) return;
const line = JSON.stringify({ ts: new Date().toISOString(), hook: "posttooluse-telemetry", event, ...data }) + "\n";
process.stderr.write(line);
try { appendFileSync(DBG_FILE, line); } catch {}
}
function parseStdin(): Record<string, unknown> | null {
try {
const raw = readFileSync(0, "utf-8").trim();
@@ -15,22 +29,38 @@ function parseStdin(): Record<string, unknown> | null {
}
async function main(): Promise<void> {
// Always log — unconditional, so we can verify the hook runs at all
try { appendFileSync(DBG_FILE, JSON.stringify({ ts: new Date().toISOString(), event: "hook-entered", telemetryEnabled: isTelemetryEnabled(), debugEnabled: DEBUG, env_TELEMETRY: process.env.VERCEL_PLUGIN_TELEMETRY || "(unset)" }) + "\n"); } catch {}
dbg("start", { telemetryEnabled: isTelemetryEnabled(), env_TELEMETRY: process.env.VERCEL_PLUGIN_TELEMETRY || "(unset)" });
if (!isTelemetryEnabled()) {
dbg("bail", { reason: "telemetry_disabled" });
process.stdout.write("{}");
process.exit(0);
}
const input = parseStdin();
if (!input) {
dbg("bail", { reason: "stdin_empty_or_invalid" });
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) || "";
const sessionId = (input.session_id as string) || (input.conversation_id as string) || "";
dbg("parsed", {
toolName,
hasSessionId: !!(input.session_id),
hasConversationId: !!(input.conversation_id),
resolvedSessionId: sessionId.slice(0, 20),
inputKeys: Object.keys(input),
});
if (!sessionId) {
dbg("bail", { reason: "no_session_id" });
process.stdout.write("{}");
process.exit(0);
}
@@ -64,8 +94,13 @@ async function main(): Promise<void> {
);
}
dbg("entries", { count: entries.length, keys: entries.map(e => e.key) });
if (entries.length > 0) {
await trackEvents(sessionId, entries);
dbg("sent", { count: entries.length, sessionId: sessionId.slice(0, 20) });
} else {
dbg("skip", { reason: "no_entries", toolName });
}
process.stdout.write("{}");
+35 -4
View File
@@ -21,13 +21,33 @@ function truncateValue(value: string): string {
return truncated + TRUNCATION_SUFFIX;
}
import { appendFileSync, readFileSync } from "node:fs";
import { resolve, join } from "node:path";
import { tmpdir, homedir } from "node:os";
const DEBUG = ["debug", "trace"].includes(process.env.VERCEL_PLUGIN_LOG_LEVEL || "")
|| process.env.VERCEL_PLUGIN_DEBUG === "1"
|| process.env.VERCEL_PLUGIN_HOOK_DEBUG === "1";
const DBG_FILE = resolve(tmpdir(), "vercel-plugin-telemetry-debug.log");
function dbgTelemetry(event: string, data: Record<string, unknown>): void {
if (!DEBUG) return;
const line = JSON.stringify({ ts: new Date().toISOString(), lib: "telemetry", event, ...data }) + "\n";
process.stderr.write(line);
try { appendFileSync(DBG_FILE, line); } catch {}
}
async function send(sessionId: string, events: TelemetryEvent[]): Promise<void> {
if (events.length === 0) return;
const bodyBytes = Buffer.byteLength(JSON.stringify(events), "utf-8");
dbgTelemetry("send-start", { sessionId: sessionId.slice(0, 20), eventCount: events.length, bodyBytes, keys: events.map(e => e.key) });
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), FLUSH_TIMEOUT_MS);
try {
await fetch(BRIDGE_ENDPOINT, {
const res = await fetch(BRIDGE_ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -37,15 +57,26 @@ async function send(sessionId: string, events: TelemetryEvent[]): Promise<void>
body: JSON.stringify(events),
signal: controller.signal,
});
} catch {
// Best-effort
dbgTelemetry("send-response", { status: res.status, ok: res.ok, statusText: res.statusText });
} catch (err) {
dbgTelemetry("send-error", { error: (err as Error)?.message || String(err) });
} finally {
clearTimeout(timeout);
}
}
export function isTelemetryEnabled(): boolean {
return process.env.VERCEL_PLUGIN_TELEMETRY === "on";
if (process.env.VERCEL_PLUGIN_TELEMETRY === "on") return true;
// Fallback: read the preference file directly in case the env var
// wasn't propagated to this process (new session, env file not sourced yet)
try {
const prefPath = join(homedir(), ".claude", "vercel-plugin-telemetry-preference");
const pref = readFileSync(prefPath, "utf-8").trim();
return pref === "enabled";
} catch {
return false;
}
}
export async function trackEvent(sessionId: string, key: string, value: string): Promise<void> {
+28 -3
View File
@@ -1,5 +1,8 @@
// hooks/src/telemetry.mts
import { randomUUID } from "crypto";
import { appendFileSync, readFileSync } from "fs";
import { resolve, join } from "path";
import { tmpdir, homedir } from "os";
var MAX_VALUE_BYTES = 1e5;
var TRUNCATION_SUFFIX = "[TRUNCATED]";
var BRIDGE_ENDPOINT = "https://telemetry.vercel.com/api/vercel-plugin/v1/events";
@@ -11,12 +14,25 @@ function truncateValue(value) {
const truncated = Buffer.from(value, "utf-8").subarray(0, MAX_VALUE_BYTES).toString("utf-8");
return truncated + TRUNCATION_SUFFIX;
}
var DEBUG = ["debug", "trace"].includes(process.env.VERCEL_PLUGIN_LOG_LEVEL || "") || process.env.VERCEL_PLUGIN_DEBUG === "1" || process.env.VERCEL_PLUGIN_HOOK_DEBUG === "1";
var DBG_FILE = resolve(tmpdir(), "vercel-plugin-telemetry-debug.log");
function dbgTelemetry(event, data) {
if (!DEBUG) return;
const line = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), lib: "telemetry", event, ...data }) + "\n";
process.stderr.write(line);
try {
appendFileSync(DBG_FILE, line);
} catch {
}
}
async function send(sessionId, events) {
if (events.length === 0) return;
const bodyBytes = Buffer.byteLength(JSON.stringify(events), "utf-8");
dbgTelemetry("send-start", { sessionId: sessionId.slice(0, 20), eventCount: events.length, bodyBytes, keys: events.map((e) => e.key) });
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), FLUSH_TIMEOUT_MS);
try {
await fetch(BRIDGE_ENDPOINT, {
const res = await fetch(BRIDGE_ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -26,13 +42,22 @@ async function send(sessionId, events) {
body: JSON.stringify(events),
signal: controller.signal
});
} catch {
dbgTelemetry("send-response", { status: res.status, ok: res.ok, statusText: res.statusText });
} catch (err) {
dbgTelemetry("send-error", { error: err?.message || String(err) });
} finally {
clearTimeout(timeout);
}
}
function isTelemetryEnabled() {
return process.env.VERCEL_PLUGIN_TELEMETRY === "on";
if (process.env.VERCEL_PLUGIN_TELEMETRY === "on") return true;
try {
const prefPath = join(homedir(), ".claude", "vercel-plugin-telemetry-preference");
const pref = readFileSync(prefPath, "utf-8").trim();
return pref === "enabled";
} catch {
return false;
}
}
async function trackEvent(sessionId, key, value) {
if (!isTelemetryEnabled()) return;
+1
View File
@@ -1,5 +1,6 @@
{
"name": "vercel-plugin",
"version": "0.2.1",
"private": true,
"bin": {
"vercel-plugin": "src/cli/index.ts"
+2 -1
View File
@@ -1,3 +1,4 @@
{
"outputDirectory": "generated"
"outputDirectory": "generated",
"cleanUrls": true
}