@blaxel/core 0.3.30-preview.323 → 0.3.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,6 +37,7 @@ exports.ensureAutoloaded = ensureAutoloaded;
37
37
  const client_gen_js_1 = require("../client/client.gen.js");
38
38
  const client_gen_js_2 = require("../sandbox/client/client.gen.js");
39
39
  const controlPlaneFetch_js_1 = require("./controlPlaneFetch.js");
40
+ const posthog_js_1 = require("./posthog.js");
40
41
  const sentry_js_1 = require("./sentry.js");
41
42
  const settings_js_1 = require("./settings.js");
42
43
  let autoloaded = false;
@@ -74,6 +75,13 @@ function ensureAutoloaded() {
74
75
  client_gen_js_2.client.setConfig({ baseUrl: settings_js_1.settings.baseUrl });
75
76
  // Initialize Sentry for SDK error tracking.
76
77
  (0, sentry_js_1.initSentry)();
78
+ // Track SDK installation (fires once per new version).
79
+ try {
80
+ (0, posthog_js_1.trackSDKInstalled)();
81
+ }
82
+ catch {
83
+ // Silently ignore - telemetry should never break the SDK
84
+ }
77
85
  // Background H2 connection warming (Node.js only)
78
86
  const isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
79
87
  /* eslint-disable */
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.POSTHOG_FLUSH_BUDGET_MS = void 0;
4
+ exports.mergeTelemetryState = mergeTelemetryState;
5
+ exports.createSDKInstallTracker = createSDKInstallTracker;
6
+ exports.trackSDKInstalled = trackSDKInstalled;
7
+ const node_js_1 = require("../common/node.js");
8
+ const settings_js_1 = require("./settings.js");
9
+ // PostHog API key injected at build time via build:replace-imports
10
+ const BUILD_POSTHOG_KEY = "";
11
+ // PostHog API endpoint
12
+ const POSTHOG_HOST = "https://us.i.posthog.com";
13
+ /**
14
+ * How long a capture may stay outstanding.
15
+ *
16
+ * Node keeps the event loop alive while a fetch is pending, so this is what a
17
+ * short-lived script waits for before it can exit. A capture against
18
+ * us.i.posthog.com takes ~250-350ms end to end (DNS + TLS handshake + POST), so
19
+ * a second covers the happy path with headroom. The ceiling matters because a
20
+ * version is only recorded after a successful delivery: when the endpoint is
21
+ * unreachable every later run tries again, and networks that silently drop
22
+ * traffic rather than refusing it would otherwise pay the full timeout each
23
+ * time.
24
+ */
25
+ exports.POSTHOG_FLUSH_BUDGET_MS = 1000;
26
+ /** The single per-language entry this SDK owns in the shared telemetry file. */
27
+ const SDK_STATE_KEY = "typescript";
28
+ let telemetryState = null;
29
+ /**
30
+ * Get the PostHog API key (injected at build time).
31
+ */
32
+ function getPosthogKey() {
33
+ const key = BUILD_POSTHOG_KEY;
34
+ // If the placeholder was not replaced, treat as empty
35
+ if (!key || key.startsWith("__BUILD_")) {
36
+ return "";
37
+ }
38
+ return key;
39
+ }
40
+ /**
41
+ * Get the telemetry file path.
42
+ */
43
+ function getTelemetryPath() {
44
+ if (node_js_1.os === null || node_js_1.path === null) {
45
+ return null;
46
+ }
47
+ try {
48
+ return node_js_1.path.join(node_js_1.os.homedir(), ".blaxel", "telemetry.json");
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ /**
55
+ * Load telemetry state from disk.
56
+ */
57
+ function loadTelemetryState() {
58
+ if (telemetryState !== null) {
59
+ return telemetryState;
60
+ }
61
+ telemetryState = { distinct_id: "", sdks: {} };
62
+ if (node_js_1.fs === null) {
63
+ return telemetryState;
64
+ }
65
+ const telemetryPath = getTelemetryPath();
66
+ if (!telemetryPath) {
67
+ return telemetryState;
68
+ }
69
+ try {
70
+ const data = node_js_1.fs.readFileSync(telemetryPath, "utf8");
71
+ const parsed = JSON.parse(data);
72
+ telemetryState = {
73
+ ...parsed,
74
+ distinct_id: parsed.distinct_id || "",
75
+ sdks: parsed.sdks || {},
76
+ };
77
+ }
78
+ catch {
79
+ // File doesn't exist or is invalid - use defaults
80
+ }
81
+ return telemetryState;
82
+ }
83
+ /**
84
+ * Combine this process's telemetry snapshot with what is on disk right now.
85
+ *
86
+ * `~/.blaxel/telemetry.json` is shared with the CLI and the Python SDK, and
87
+ * each of them caches it in memory for the lifetime of its process. Writing
88
+ * this snapshot back wholesale would roll back anything the others recorded in
89
+ * the meantime, which makes them re-send their "Installed" event on every later
90
+ * run. Anything already on disk is at least as fresh as what is held here, so
91
+ * it wins, except for the fields this process actually owns.
92
+ */
93
+ function mergeTelemetryState(onDisk, ours) {
94
+ const merged = { ...ours, ...onDisk };
95
+ // Only re-assert the one language entry this process owns. Writing back the
96
+ // whole cached map would roll back a newer version another SDK recorded
97
+ // after this process started, and that SDK would then re-send its
98
+ // "Installed" event.
99
+ const onDiskSdks = typeof onDisk.sdks === "object" && onDisk.sdks !== null ? onDisk.sdks : {};
100
+ const ownVersion = ours.sdks?.[SDK_STATE_KEY];
101
+ merged.sdks = {
102
+ ...onDiskSdks,
103
+ ...(ownVersion ? { [SDK_STATE_KEY]: ownVersion } : {}),
104
+ };
105
+ // distinct_id is shared by every writer, so the first one persisted wins.
106
+ // Replacing it would split the same user across two PostHog identities.
107
+ const onDiskId = typeof onDisk.distinct_id === "string" ? onDisk.distinct_id : "";
108
+ merged.distinct_id = onDiskId || ours.distinct_id;
109
+ return merged;
110
+ }
111
+ /**
112
+ * Save telemetry state to disk, preserving concurrent writes from the CLI and
113
+ * the other SDK.
114
+ */
115
+ function saveTelemetryState(state) {
116
+ if (node_js_1.fs === null || node_js_1.path === null || node_js_1.os === null) {
117
+ return;
118
+ }
119
+ const telemetryPath = getTelemetryPath();
120
+ if (!telemetryPath) {
121
+ return;
122
+ }
123
+ try {
124
+ const dir = node_js_1.path.dirname(telemetryPath);
125
+ if (!node_js_1.fs.existsSync(dir)) {
126
+ node_js_1.fs.mkdirSync(dir, { recursive: true });
127
+ }
128
+ // Re-read immediately before writing rather than trusting the snapshot
129
+ // loaded when this process started.
130
+ let onDisk = {};
131
+ try {
132
+ const parsed = JSON.parse(node_js_1.fs.readFileSync(telemetryPath, "utf8"));
133
+ if (typeof parsed === "object" && parsed !== null) {
134
+ onDisk = parsed;
135
+ }
136
+ }
137
+ catch {
138
+ // No readable file yet - this process's state is all there is.
139
+ }
140
+ const merged = mergeTelemetryState(onDisk, state);
141
+ node_js_1.fs.writeFileSync(telemetryPath, JSON.stringify(merged, null, 2), {
142
+ mode: 0o600,
143
+ });
144
+ // Adopt the persisted identity so events match what other writers use.
145
+ if (typeof merged.distinct_id === "string" && merged.distinct_id) {
146
+ state.distinct_id = merged.distinct_id;
147
+ }
148
+ }
149
+ catch {
150
+ // Silently fail
151
+ }
152
+ }
153
+ /**
154
+ * Generate a UUID v4 without external dependencies.
155
+ */
156
+ function generateUUID() {
157
+ const bytes = new Uint8Array(16);
158
+ crypto.getRandomValues(bytes);
159
+ bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4
160
+ bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 10
161
+ const hex = Array.from(bytes)
162
+ .map((b) => b.toString(16).padStart(2, "0"))
163
+ .join("");
164
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
165
+ }
166
+ /**
167
+ * Get or create a persistent distinct ID for PostHog events.
168
+ */
169
+ function getDistinctID() {
170
+ const state = loadTelemetryState();
171
+ if (state.distinct_id) {
172
+ return state.distinct_id;
173
+ }
174
+ state.distinct_id = generateUUID();
175
+ saveTelemetryState(state);
176
+ return state.distinct_id;
177
+ }
178
+ /**
179
+ * Create an SDK install tracker. Exported from this internal module so delivery
180
+ * and deduplication behavior can be tested without build-time credentials.
181
+ */
182
+ function createSDKInstallTracker(options) {
183
+ const pendingVersions = new Set();
184
+ return function track() {
185
+ const apiKey = options.getApiKey();
186
+ if (!apiKey || !options.isTrackingEnabled() || !options.isNode()) {
187
+ return;
188
+ }
189
+ const version = options.getVersion();
190
+ if (!version ||
191
+ version === "unknown" ||
192
+ version === "__BUILD_VERSION__") {
193
+ return;
194
+ }
195
+ const state = options.loadState();
196
+ const stateKey = SDK_STATE_KEY;
197
+ if (state.sdks?.[stateKey] === version || pendingVersions.has(version)) {
198
+ return;
199
+ }
200
+ pendingVersions.add(version);
201
+ return (async () => {
202
+ try {
203
+ const payload = {
204
+ api_key: apiKey,
205
+ event: "Installed SDK",
206
+ distinct_id: options.getDistinctId(),
207
+ timestamp: new Date().toISOString(),
208
+ properties: {
209
+ language: "typescript",
210
+ sdk: "core",
211
+ version,
212
+ },
213
+ };
214
+ const response = await options.fetch(`${POSTHOG_HOST}/capture/`, {
215
+ method: "POST",
216
+ headers: { "Content-Type": "application/json" },
217
+ body: JSON.stringify(payload),
218
+ signal: options.getSignal(),
219
+ });
220
+ if (!response.ok) {
221
+ return;
222
+ }
223
+ state.sdks ??= {};
224
+ state.sdks[stateKey] = version;
225
+ options.saveState(state);
226
+ }
227
+ catch {
228
+ // Telemetry must never break the SDK. A later call may retry.
229
+ }
230
+ finally {
231
+ pendingVersions.delete(version);
232
+ }
233
+ })();
234
+ };
235
+ }
236
+ const sdkInstallTracker = createSDKInstallTracker({
237
+ getApiKey: getPosthogKey,
238
+ isTrackingEnabled: () => settings_js_1.settings.tracking,
239
+ getVersion: () => settings_js_1.settings.version,
240
+ isNode: () => typeof process !== "undefined" && Boolean(process.versions?.node),
241
+ loadState: loadTelemetryState,
242
+ saveState: saveTelemetryState,
243
+ getDistinctId: getDistinctID,
244
+ fetch: (...args) => globalThis.fetch(...args),
245
+ getSignal: () => typeof AbortSignal !== "undefined" &&
246
+ typeof AbortSignal.timeout === "function"
247
+ ? AbortSignal.timeout(exports.POSTHOG_FLUSH_BUDGET_MS)
248
+ : undefined,
249
+ });
250
+ /**
251
+ * Track SDK installation. Fires "Installed SDK" once per new version.
252
+ * Called during SDK autoload.
253
+ */
254
+ function trackSDKInstalled() {
255
+ void sdkInstallTracker();
256
+ }
@@ -30,8 +30,8 @@ function missingCredentialsMessage() {
30
30
  return "No Blaxel credentials found. Set the BL_API_KEY and BL_WORKSPACE environment variables, or run `bl login`.";
31
31
  }
32
32
  // Build info - these placeholders are replaced at build time by build:replace-imports
33
- const BUILD_VERSION = "0.3.30-preview.323";
34
- const BUILD_COMMIT = "999bd1ab9bdf3a42c60a8d62a89f5cb21a520c30";
33
+ const BUILD_VERSION = "0.3.30";
34
+ const BUILD_COMMIT = "e88b0db8a85b1db102dfedce7af3e5f5ebae1f52";
35
35
  const BUILD_SENTRY_DSN = "https://fd5e60e1c9820e1eef5ccebb84a07127@o4508714045276160.ingest.us.sentry.io/4510465864564736";
36
36
  const BLAXEL_API_VERSION = "2026-09-22";
37
37
  // Bun < 1.3.11 never sends connection-level WINDOW_UPDATE: the pooled h2
@@ -0,0 +1,51 @@
1
+ /**
2
+ * How long a capture may stay outstanding.
3
+ *
4
+ * Node keeps the event loop alive while a fetch is pending, so this is what a
5
+ * short-lived script waits for before it can exit. A capture against
6
+ * us.i.posthog.com takes ~250-350ms end to end (DNS + TLS handshake + POST), so
7
+ * a second covers the happy path with headroom. The ceiling matters because a
8
+ * version is only recorded after a successful delivery: when the endpoint is
9
+ * unreachable every later run tries again, and networks that silently drop
10
+ * traffic rather than refusing it would otherwise pay the full timeout each
11
+ * time.
12
+ */
13
+ export declare const POSTHOG_FLUSH_BUDGET_MS = 1000;
14
+ type TelemetryState = {
15
+ distinct_id: string;
16
+ cli?: string;
17
+ sdks?: Record<string, string>;
18
+ };
19
+ /**
20
+ * Combine this process's telemetry snapshot with what is on disk right now.
21
+ *
22
+ * `~/.blaxel/telemetry.json` is shared with the CLI and the Python SDK, and
23
+ * each of them caches it in memory for the lifetime of its process. Writing
24
+ * this snapshot back wholesale would roll back anything the others recorded in
25
+ * the meantime, which makes them re-send their "Installed" event on every later
26
+ * run. Anything already on disk is at least as fresh as what is held here, so
27
+ * it wins, except for the fields this process actually owns.
28
+ */
29
+ export declare function mergeTelemetryState(onDisk: Record<string, unknown>, ours: TelemetryState): Record<string, unknown>;
30
+ type SDKInstallTrackerOptions = {
31
+ getApiKey: () => string;
32
+ isTrackingEnabled: () => boolean;
33
+ getVersion: () => string;
34
+ isNode: () => boolean;
35
+ loadState: () => TelemetryState;
36
+ saveState: (state: TelemetryState) => void;
37
+ getDistinctId: () => string;
38
+ fetch: typeof globalThis.fetch;
39
+ getSignal: () => AbortSignal | undefined;
40
+ };
41
+ /**
42
+ * Create an SDK install tracker. Exported from this internal module so delivery
43
+ * and deduplication behavior can be tested without build-time credentials.
44
+ */
45
+ export declare function createSDKInstallTracker(options: SDKInstallTrackerOptions): () => Promise<void> | undefined;
46
+ /**
47
+ * Track SDK installation. Fires "Installed SDK" once per new version.
48
+ * Called during SDK autoload.
49
+ */
50
+ export declare function trackSDKInstalled(): void;
51
+ export {};