Files
wehub-resource-sync 555e282cc4
pi-agent-plugin checks / lint (push) Has been cancelled
pi-agent-plugin checks / test (20) (push) Has been cancelled
pi-agent-plugin checks / test (22) (push) Has been cancelled
pi-agent-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / check_changes (push) Has been cancelled
TypeScript SDK CI / changelog_check (push) Has been cancelled
ci / changelog_check (push) Has been cancelled
ci / check_changes (push) Has been cancelled
ci / build_mem0 (3.10) (push) Has been cancelled
ci / build_mem0 (3.11) (push) Has been cancelled
ci / build_mem0 (3.12) (push) Has been cancelled
CLI Node CI / lint (push) Has been cancelled
CLI Node CI / test (20) (push) Has been cancelled
CLI Node CI / test (22) (push) Has been cancelled
CLI Node CI / build (push) Has been cancelled
CLI Python CI / lint (push) Has been cancelled
CLI Python CI / test (3.10) (push) Has been cancelled
CLI Python CI / test (3.11) (push) Has been cancelled
CLI Python CI / test (3.12) (push) Has been cancelled
CLI Python CI / build (push) Has been cancelled
openclaw checks / lint (push) Has been cancelled
openclaw checks / test (20) (push) Has been cancelled
openclaw checks / test (22) (push) Has been cancelled
openclaw checks / build (push) Has been cancelled
opencode-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (22) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (22) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:45 +08:00

114 lines
3.8 KiB
TypeScript

/**
* Plugin telemetry for the Mem0 OpenCode plugin — anonymous usage tracking
* via PostHog.
*
* Emits the SAME event schema as the Mem0 editor plugin's telemetry.py
* (event names prefixed `plugin.`, `source: "plugin"`, `platform: "opencode"`,
* `distinct_id = sha256(apiKey)[:32]`) so OpenCode shows up as just another
* `platform` value in the shared plugin dashboard instead of a separate
* event namespace.
*
* Fire-and-forget: never throws, never blocks, failures are swallowed. Only
* fires when an API key is present (same as the editor plugin — anonymous
* installs without a key emit nothing). Disable with MEM0_TELEMETRY=false.
*
* Never sends: memory content, API keys, raw user/project IDs. Only sends:
* event type, platform, plugin version, and anonymized hashes of the API key
* and project ID.
*/
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { release } from "node:os";
const POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX";
const POSTHOG_HOST = "https://us.i.posthog.com/i/v0/e/";
const REQUEST_TIMEOUT_MS = 2_000;
function _loadPluginVersion(): string {
// Source context: telemetry.ts sits next to package.json (./).
// Bundled context: dist/index.js sits one level below it (../).
for (const rel of ["./package.json", "../package.json"]) {
try {
const pkg = JSON.parse(readFileSync(new URL(rel, import.meta.url), "utf-8"));
if (pkg?.name === "@mem0/opencode-plugin" && pkg.version) return pkg.version;
} catch {
/* try next candidate */
}
}
return "unknown";
}
const PLUGIN_VERSION = _loadPluginVersion();
export function isTelemetryEnabled(): boolean {
const val = process.env.MEM0_TELEMETRY;
if (val === undefined) return true;
const s = val.toLowerCase();
return s !== "false" && s !== "0" && s !== "no" && s !== "off";
}
function distinctId(apiKey: string): string {
// Matches telemetry.py `_distinct_id()` so the same user is one person in
// PostHog whether they use OpenCode or any other Mem0 editor plugin.
return createHash("sha256").update(apiKey).digest("hex").slice(0, 32);
}
/**
* Build the PostHog event payload, or null when telemetry is disabled or no
* API key is available. Pure (aside from env/version reads) and exported for
* testing. System-controlled properties are applied last so a caller cannot
* override `source`/`platform`/etc.
*/
export function buildEvent(
eventType: string,
properties: Record<string, unknown>,
apiKey: string | undefined,
projectId?: string,
): Record<string, unknown> | null {
if (!isTelemetryEnabled() || !apiKey) return null;
return {
api_key: POSTHOG_API_KEY,
distinct_id: distinctId(apiKey),
event: `plugin.${eventType}`,
properties: {
...properties,
source: "plugin",
platform: "opencode",
plugin_version: PLUGIN_VERSION,
os: process.platform,
os_version: release(),
sample_rate: 1.0,
$process_person_profile: false,
$lib: "posthog-node",
// Anonymized project segmentation, matching telemetry.py's project_hash.
...(projectId
? { project_hash: createHash("sha256").update(projectId).digest("hex") }
: {}),
},
};
}
/** Send a usage event, fire-and-forget. Never throws, never blocks. */
export function captureEvent(
eventType: string,
properties: Record<string, unknown>,
apiKey: string | undefined,
projectId?: string,
): void {
const payload = buildEvent(eventType, properties, apiKey, projectId);
if (!payload) return;
try {
void fetch(POSTHOG_HOST, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
}).catch(() => {
/* fire-and-forget */
});
} catch {
/* never throw */
}
}