kumarabhirup 351b71fd05
feat(telemetry): add person identity support and enable session replay
Add optional name, email, avatar, and denchOrgId fields to
telemetry.json. When present, all telemetry layers (CLI, web server,
web client, OpenClaw plugin) call PostHog identify() with $name,
$email, $avatar, and dench_org_id person properties.

Remove $process_person_profile:false from all layers so every install
gets a PostHog person profile. Enable session replay with masking
controlled by privacy mode (all text/inputs masked when on, nothing
masked when off).
2026-03-18 00:08:23 -07:00

106 lines
2.6 KiB
TypeScript

const DEFAULT_HOST = "https://us.i.posthog.com";
const FLUSH_INTERVAL_MS = 15_000;
const FLUSH_AT = 10;
export interface CaptureEvent {
distinctId: string;
event: string;
properties?: Record<string, unknown>;
}
/**
* Minimal PostHog client using the HTTP capture API directly.
* Zero npm dependencies -- uses built-in fetch (Node 18+).
*/
export class PostHogClient {
private apiKey: string;
private host: string;
private globalProperties: Record<string, unknown>;
private queue: Array<Record<string, unknown>> = [];
private timer: ReturnType<typeof setInterval> | null = null;
constructor(apiKey: string, host?: string, globalProperties?: Record<string, unknown>) {
this.apiKey = apiKey;
this.host = (host || DEFAULT_HOST).replace(/\/$/, "");
this.globalProperties = globalProperties ?? {};
this.timer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
if (this.timer.unref) this.timer.unref();
}
capture(event: CaptureEvent): void {
this.queue.push({
event: event.event,
distinct_id: event.distinctId,
properties: {
...this.globalProperties,
...event.properties,
$lib: "denchclaw-posthog-plugin",
},
timestamp: new Date().toISOString(),
});
if (this.queue.length >= FLUSH_AT) {
this.flush();
}
}
flush(): void {
if (this.queue.length === 0) return;
const batch = this.queue.splice(0);
const body = JSON.stringify({
api_key: this.apiKey,
batch,
});
fetch(`${this.host}/batch/`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body,
}).catch(() => {
// Fail silently -- telemetry should never block the gateway.
});
}
identify(distinctId: string, properties: Record<string, unknown>): void {
this.queue.push({
event: "$identify",
distinct_id: distinctId,
properties: {
...this.globalProperties,
$set: properties,
$lib: "denchclaw-posthog-plugin",
},
timestamp: new Date().toISOString(),
});
if (this.queue.length >= FLUSH_AT) {
this.flush();
}
}
async shutdown(): Promise<void> {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
this.flush();
}
}
export function createPostHogClient(
apiKey: string,
host?: string,
globalProperties?: Record<string, unknown>,
): PostHogClient {
return new PostHogClient(apiKey, host, globalProperties);
}
export async function shutdownPostHogClient(client: PostHogClient): Promise<void> {
try {
await client.shutdown();
} catch {
// Non-fatal.
}
}