Files
admin 875c7f9b91 feat: Complete zCode CLI X with Telegram bot integration
- Add full Telegram bot functionality with Z.AI API integration
- Implement 4 tools: Bash, FileEdit, WebSearch, Git
- Add 3 agents: Code Reviewer, Architect, DevOps Engineer
- Add 6 skills for common coding tasks
- Add systemd service file for 24/7 operation
- Add nginx configuration for HTTPS webhook
- Add comprehensive documentation
- Implement WebSocket server for real-time updates
- Add logging system with Winston
- Add environment validation

🤖 zCode CLI X - Agentic coder with Z.AI + Telegram integration
2026-05-05 09:01:26 +00:00

215 lines
6.0 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.growthbookTrackingPlugin = growthbookTrackingPlugin;
var _util = require("../util");
var _core = require("../core");
const SDK_VERSION = (0, _util.loadSDKVersion)();
function parseString(value) {
return typeof value === "string" ? value : null;
}
function parseAttributes(attributes) {
const {
user_id,
device_id,
anonymous_id,
id,
page_id,
session_id,
utmCampaign,
utmContent,
utmMedium,
utmSource,
utmTerm,
pageTitle,
...nested
} = attributes;
return {
nested,
topLevel: {
user_id: parseString(user_id),
device_id: parseString(device_id || anonymous_id || id),
page_id: parseString(page_id),
session_id: parseString(session_id),
utm_campaign: parseString(utmCampaign) || undefined,
utm_content: parseString(utmContent) || undefined,
utm_medium: parseString(utmMedium) || undefined,
utm_source: parseString(utmSource) || undefined,
utm_term: parseString(utmTerm) || undefined,
page_title: parseString(pageTitle) || undefined
}
};
}
function getEventPayload({
eventName,
properties,
attributes,
url
}) {
const {
nested,
topLevel
} = parseAttributes(attributes || {});
return {
event_name: eventName,
properties_json: properties || {},
...topLevel,
sdk_language: "js",
sdk_version: SDK_VERSION,
url: url,
context_json: nested
};
}
async function track({
clientKey,
ingestorHost,
events
}) {
if (!events.length) return;
const endpoint = `${ingestorHost || "https://us1.gb-ingest.com"}/track?client_key=${clientKey}`;
const body = JSON.stringify(events);
try {
await fetch(endpoint, {
method: "POST",
body,
headers: {
Accept: "application/json",
"Content-Type": "text/plain"
},
credentials: "omit"
});
} catch (e) {
console.error("Failed to track event", e);
}
}
function growthbookTrackingPlugin({
queueFlushInterval = 100,
ingestorHost,
enable = true,
debug,
dedupeCacheSize = 1000,
dedupeKeyAttributes = [],
eventFilter
} = {}) {
return gb => {
const clientKey = gb.getClientKey();
if (!clientKey) {
throw new Error("clientKey must be specified to use event logging");
}
// LRU cache for events to avoid duplicates
const eventCache = new Set();
if ("setEventLogger" in gb) {
let _q = [];
let timer = null;
const flush = async () => {
const events = _q;
_q = [];
timer && clearTimeout(timer);
timer = null;
events.length && (await track({
clientKey,
events,
ingestorHost
}));
};
let promise = null;
gb.setEventLogger(async (eventName, properties, userContext) => {
const data = {
eventName,
properties,
attributes: userContext.attributes || {},
url: userContext.url || ""
};
// Skip logging if the event is being filtered
if (eventFilter && !eventFilter(data)) {
return;
}
// De-dupe Feature Evaluated and Experiment Viewed events
if (eventName === _core.EVENT_FEATURE_EVALUATED || eventName === _core.EVENT_EXPERIMENT_VIEWED) {
// Build the key for de-duping
const dedupeKeyData = {
eventName,
properties
};
for (const key of dedupeKeyAttributes) {
dedupeKeyData["attr:" + key] = data.attributes[key];
}
const k = JSON.stringify(dedupeKeyData);
// Duplicate event fired recently, move to end of LRU cache and skip
if (eventCache.has(k)) {
eventCache.delete(k);
eventCache.add(k);
return;
}
eventCache.add(k);
// If the cache is too big, remove the oldest item
if (eventCache.size > dedupeCacheSize) {
const oldest = eventCache.values().next().value;
oldest && eventCache.delete(oldest);
}
}
const payload = getEventPayload(data);
debug && console.log("Logging event to GrowthBook", JSON.parse(JSON.stringify(payload)));
if (!enable) return;
_q.push(payload);
// Only one in-progress promise at a time
if (!promise) {
promise = new Promise((resolve, reject) => {
// Flush the queue after a delay
timer = setTimeout(() => {
flush().then(resolve).catch(reject);
promise = null;
}, queueFlushInterval);
});
}
await promise;
});
// Flush the queue on page unload
if (typeof document !== "undefined" && document.visibilityState) {
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "hidden") {
flush().catch(console.error);
}
});
}
// Flush the queue when the growthbook instance is destroyed
"onDestroy" in gb && gb.onDestroy(() => {
flush().catch(console.error);
});
}
// Listen on window.gbEvents.push if in a browser
// This makes it easier to integrate with Segment, GTM, etc.
if (typeof window !== "undefined" && !("createScopedInstance" in gb)) {
const prevEvents = Array.isArray(window.gbEvents) ? window.gbEvents : [];
window.gbEvents = {
push: event => {
if ("isDestroyed" in gb && gb.isDestroyed()) {
// If trying to log and the instance has been destroyed, switch back to just an array
// This will let the next GrowthBook instance pick it up
window.gbEvents = [event];
return;
}
if (typeof event === "string") {
gb.logEvent(event);
} else if (event) {
gb.logEvent(event.eventName, event.properties);
}
}
};
for (const event of prevEvents) {
window.gbEvents.push(event);
}
}
};
}
//# sourceMappingURL=growthbook-tracking.js.map