@glasstrace/sdk 0.9.1 → 0.11.0

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createBuildHash
3
- } from "./chunk-PQWAKVQ5.js";
3
+ } from "./chunk-D3JC3LAK.js";
4
4
 
5
5
  // src/import-graph.ts
6
6
  import * as fs from "fs/promises";
@@ -175,4 +175,4 @@ export {
175
175
  extractImports,
176
176
  buildImportGraph
177
177
  };
178
- //# sourceMappingURL=chunk-Y6V7BTF3.js.map
178
+ //# sourceMappingURL=chunk-M7RDFOFR.js.map
@@ -0,0 +1,417 @@
1
+ import {
2
+ PresignedUploadResponseSchema,
3
+ SourceMapManifestResponseSchema,
4
+ SourceMapUploadResponseSchema
5
+ } from "./chunk-D3JC3LAK.js";
6
+
7
+ // src/source-map-uploader.ts
8
+ import * as fs from "fs/promises";
9
+ import * as path from "path";
10
+ import * as crypto from "crypto";
11
+ import { execFileSync } from "child_process";
12
+
13
+ // src/nudge/error-nudge.ts
14
+ import { existsSync } from "fs";
15
+ import { join } from "path";
16
+
17
+ // src/env-detection.ts
18
+ var DEFAULT_ENDPOINT = "https://api.glasstrace.dev";
19
+ function readEnvVars() {
20
+ return {
21
+ GLASSTRACE_API_KEY: process.env.GLASSTRACE_API_KEY?.trim() || void 0,
22
+ GLASSTRACE_FORCE_ENABLE: process.env.GLASSTRACE_FORCE_ENABLE,
23
+ GLASSTRACE_ENV: process.env.GLASSTRACE_ENV,
24
+ GLASSTRACE_COVERAGE_MAP: process.env.GLASSTRACE_COVERAGE_MAP,
25
+ NODE_ENV: process.env.NODE_ENV,
26
+ VERCEL_ENV: process.env.VERCEL_ENV
27
+ };
28
+ }
29
+ function resolveConfig(options) {
30
+ const env = readEnvVars();
31
+ return {
32
+ apiKey: options?.apiKey ?? env.GLASSTRACE_API_KEY,
33
+ endpoint: options?.endpoint ?? DEFAULT_ENDPOINT,
34
+ forceEnable: options?.forceEnable ?? env.GLASSTRACE_FORCE_ENABLE === "true",
35
+ verbose: options?.verbose ?? false,
36
+ environment: env.GLASSTRACE_ENV,
37
+ coverageMapEnabled: env.GLASSTRACE_COVERAGE_MAP === "true",
38
+ nodeEnv: env.NODE_ENV,
39
+ vercelEnv: env.VERCEL_ENV
40
+ };
41
+ }
42
+ function isProductionDisabled(config) {
43
+ if (config.forceEnable) {
44
+ return false;
45
+ }
46
+ if (config.nodeEnv === "production") {
47
+ return true;
48
+ }
49
+ if (config.vercelEnv === "production") {
50
+ return true;
51
+ }
52
+ return false;
53
+ }
54
+ function isAnonymousMode(config) {
55
+ if (config.apiKey === void 0) {
56
+ return true;
57
+ }
58
+ if (config.apiKey.trim() === "") {
59
+ return true;
60
+ }
61
+ if (config.apiKey.startsWith("gt_anon_")) {
62
+ return true;
63
+ }
64
+ return false;
65
+ }
66
+
67
+ // src/nudge/error-nudge.ts
68
+ var hasFired = false;
69
+ function sanitize(input) {
70
+ return input.replace(/[\x00-\x1f\x7f]/g, "");
71
+ }
72
+ function maybeShowMcpNudge(errorSummary) {
73
+ if (hasFired) {
74
+ return;
75
+ }
76
+ const config = resolveConfig();
77
+ if (isProductionDisabled(config)) {
78
+ hasFired = true;
79
+ return;
80
+ }
81
+ let markerExists = false;
82
+ try {
83
+ const markerPath = join(process.cwd(), ".glasstrace", "mcp-connected");
84
+ markerExists = existsSync(markerPath);
85
+ } catch {
86
+ markerExists = false;
87
+ }
88
+ if (markerExists) {
89
+ hasFired = true;
90
+ return;
91
+ }
92
+ hasFired = true;
93
+ const safe = sanitize(errorSummary);
94
+ process.stderr.write(
95
+ `[glasstrace] Error captured: ${safe}
96
+ Debug with AI: ask your agent "What's the latest Glasstrace error?"
97
+ Not connected? Run: npx glasstrace mcp add
98
+ `
99
+ );
100
+ }
101
+
102
+ // src/console-capture.ts
103
+ var isGlasstraceLog = false;
104
+ var originalError = null;
105
+ var originalWarn = null;
106
+ var installed = false;
107
+ var otelApi = null;
108
+ function formatArgs(args) {
109
+ return args.map((arg) => {
110
+ if (typeof arg === "string") return arg;
111
+ if (arg instanceof Error) return arg.stack ?? arg.message;
112
+ try {
113
+ return JSON.stringify(arg);
114
+ } catch {
115
+ return String(arg);
116
+ }
117
+ }).join(" ");
118
+ }
119
+ function isSdkMessage(args) {
120
+ return typeof args[0] === "string" && args[0].startsWith("[glasstrace]");
121
+ }
122
+ async function installConsoleCapture() {
123
+ if (installed) return;
124
+ try {
125
+ otelApi = await import("./esm-POMEQPKL.js");
126
+ } catch {
127
+ otelApi = null;
128
+ }
129
+ originalError = console.error;
130
+ originalWarn = console.warn;
131
+ installed = true;
132
+ console.error = (...args) => {
133
+ originalError.apply(console, args);
134
+ if (isGlasstraceLog || isSdkMessage(args)) return;
135
+ if (otelApi) {
136
+ const span = otelApi.trace.getSpan(otelApi.context.active());
137
+ if (span) {
138
+ const formattedMessage = formatArgs(args);
139
+ span.addEvent("console.error", {
140
+ "console.message": formattedMessage
141
+ });
142
+ try {
143
+ maybeShowMcpNudge(formattedMessage);
144
+ } catch {
145
+ }
146
+ }
147
+ }
148
+ };
149
+ console.warn = (...args) => {
150
+ originalWarn.apply(console, args);
151
+ if (isGlasstraceLog || isSdkMessage(args)) return;
152
+ if (otelApi) {
153
+ const span = otelApi.trace.getSpan(otelApi.context.active());
154
+ if (span) {
155
+ span.addEvent("console.warn", {
156
+ "console.message": formatArgs(args)
157
+ });
158
+ }
159
+ }
160
+ };
161
+ }
162
+ function sdkLog(level, message) {
163
+ isGlasstraceLog = true;
164
+ try {
165
+ console[level](message);
166
+ } finally {
167
+ isGlasstraceLog = false;
168
+ }
169
+ }
170
+
171
+ // src/source-map-uploader.ts
172
+ async function collectSourceMaps(buildDir) {
173
+ const results = [];
174
+ try {
175
+ await walkDir(buildDir, buildDir, results);
176
+ } catch {
177
+ return [];
178
+ }
179
+ return results;
180
+ }
181
+ async function walkDir(baseDir, currentDir, results) {
182
+ let entries;
183
+ try {
184
+ entries = await fs.readdir(currentDir, { withFileTypes: true });
185
+ } catch {
186
+ return;
187
+ }
188
+ for (const entry of entries) {
189
+ const fullPath = path.join(currentDir, entry.name);
190
+ if (entry.isDirectory()) {
191
+ await walkDir(baseDir, fullPath, results);
192
+ } else if (entry.isFile() && entry.name.endsWith(".map")) {
193
+ try {
194
+ const content = await fs.readFile(fullPath, "utf-8");
195
+ const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, "/");
196
+ const compiledPath = relativePath.replace(/\.map$/, "");
197
+ results.push({ filePath: compiledPath, content });
198
+ } catch {
199
+ }
200
+ }
201
+ }
202
+ }
203
+ async function computeBuildHash(maps) {
204
+ try {
205
+ const sha = execFileSync("git", ["rev-parse", "HEAD"], { encoding: "utf-8" }).trim();
206
+ if (sha) {
207
+ return sha;
208
+ }
209
+ } catch {
210
+ }
211
+ const sortedMaps = [...maps ?? []].sort(
212
+ (a, b) => a.filePath.localeCompare(b.filePath)
213
+ );
214
+ const hashInput = sortedMaps.map((m) => `${m.filePath}
215
+ ${m.content.length}
216
+ ${m.content}`).join("");
217
+ const hash = crypto.createHash("sha256").update(hashInput).digest("hex");
218
+ return hash;
219
+ }
220
+ async function uploadSourceMaps(apiKey, endpoint, buildHash, maps) {
221
+ const body = {
222
+ apiKey,
223
+ buildHash,
224
+ files: maps.map((m) => ({
225
+ filePath: m.filePath,
226
+ sourceMap: m.content
227
+ }))
228
+ };
229
+ const baseUrl = stripTrailingSlashes(endpoint);
230
+ const response = await fetch(`${baseUrl}/v1/source-maps`, {
231
+ method: "POST",
232
+ headers: {
233
+ "Content-Type": "application/json",
234
+ Authorization: `Bearer ${apiKey}`
235
+ },
236
+ body: JSON.stringify(body)
237
+ });
238
+ if (!response.ok) {
239
+ try {
240
+ await response.text();
241
+ } catch {
242
+ }
243
+ throw new Error(
244
+ `Source map upload failed: ${String(response.status)} ${response.statusText}`
245
+ );
246
+ }
247
+ const json = await response.json();
248
+ return SourceMapUploadResponseSchema.parse(json);
249
+ }
250
+ var PRESIGNED_THRESHOLD_BYTES = 45e5;
251
+ function stripTrailingSlashes(url) {
252
+ let result = url;
253
+ while (result.endsWith("/")) {
254
+ result = result.slice(0, -1);
255
+ }
256
+ return result;
257
+ }
258
+ async function requestPresignedTokens(apiKey, endpoint, buildHash, files) {
259
+ const baseUrl = stripTrailingSlashes(endpoint);
260
+ const response = await fetch(`${baseUrl}/v1/source-maps/presign`, {
261
+ method: "POST",
262
+ headers: {
263
+ "Content-Type": "application/json",
264
+ Authorization: `Bearer ${apiKey}`
265
+ },
266
+ body: JSON.stringify({ buildHash, files })
267
+ });
268
+ if (!response.ok) {
269
+ try {
270
+ await response.text();
271
+ } catch {
272
+ }
273
+ throw new Error(
274
+ `Presigned token request failed: ${String(response.status)} ${response.statusText}`
275
+ );
276
+ }
277
+ const json = await response.json();
278
+ return PresignedUploadResponseSchema.parse(json);
279
+ }
280
+ async function uploadToBlob(clientToken, pathname, content) {
281
+ let mod;
282
+ try {
283
+ mod = await import("@vercel/blob/client");
284
+ } catch (err) {
285
+ const code = err.code;
286
+ if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
287
+ throw new Error(
288
+ "Presigned upload requires @vercel/blob. Install it: npm install @vercel/blob"
289
+ );
290
+ }
291
+ throw err;
292
+ }
293
+ const result = await mod.put(pathname, new Blob([content]), {
294
+ access: "public",
295
+ token: clientToken
296
+ });
297
+ return { url: result.url, size: Buffer.byteLength(content, "utf-8") };
298
+ }
299
+ async function submitManifest(apiKey, endpoint, uploadId, buildHash, files) {
300
+ const baseUrl = stripTrailingSlashes(endpoint);
301
+ const response = await fetch(`${baseUrl}/v1/source-maps/manifest`, {
302
+ method: "POST",
303
+ headers: {
304
+ "Content-Type": "application/json",
305
+ Authorization: `Bearer ${apiKey}`
306
+ },
307
+ body: JSON.stringify({ uploadId, buildHash, files })
308
+ });
309
+ if (!response.ok) {
310
+ try {
311
+ await response.text();
312
+ } catch {
313
+ }
314
+ throw new Error(
315
+ `Source map manifest submission failed: ${String(response.status)} ${response.statusText}`
316
+ );
317
+ }
318
+ const json = await response.json();
319
+ return SourceMapManifestResponseSchema.parse(json);
320
+ }
321
+ async function uploadSourceMapsPresigned(apiKey, endpoint, buildHash, maps, blobUploader = uploadToBlob) {
322
+ if (maps.length === 0) {
323
+ throw new Error("No source maps to upload");
324
+ }
325
+ const presigned = await requestPresignedTokens(
326
+ apiKey,
327
+ endpoint,
328
+ buildHash,
329
+ maps.map((m) => ({
330
+ filePath: m.filePath,
331
+ sizeBytes: Buffer.byteLength(m.content, "utf-8")
332
+ }))
333
+ );
334
+ const mapsByPath = new Map(maps.map((m) => [m.filePath, m]));
335
+ if (mapsByPath.size !== maps.length) {
336
+ throw new Error("Duplicate filePath entries in source maps");
337
+ }
338
+ for (const token of presigned.files) {
339
+ if (!mapsByPath.has(token.filePath)) {
340
+ throw new Error(
341
+ `Presigned token for "${token.filePath}" has no matching source map entry`
342
+ );
343
+ }
344
+ }
345
+ const CONCURRENCY = 5;
346
+ const uploadResults = [];
347
+ for (let i = 0; i < presigned.files.length; i += CONCURRENCY) {
348
+ const chunk = presigned.files.slice(i, i + CONCURRENCY);
349
+ const chunkResults = await Promise.all(
350
+ chunk.map(async (token) => {
351
+ const entry = mapsByPath.get(token.filePath);
352
+ const result = await blobUploader(token.clientToken, token.pathname, entry.content);
353
+ return {
354
+ filePath: token.filePath,
355
+ sizeBytes: result.size,
356
+ blobUrl: result.url
357
+ };
358
+ })
359
+ );
360
+ uploadResults.push(...chunkResults);
361
+ }
362
+ return submitManifest(apiKey, endpoint, presigned.uploadId, buildHash, uploadResults);
363
+ }
364
+ async function uploadSourceMapsAuto(apiKey, endpoint, buildHash, maps, options) {
365
+ if (maps.length === 0) {
366
+ throw new Error("No source maps to upload");
367
+ }
368
+ const totalBytes = maps.reduce(
369
+ (sum, m) => sum + Buffer.byteLength(m.content, "utf-8"),
370
+ 0
371
+ );
372
+ if (totalBytes < PRESIGNED_THRESHOLD_BYTES) {
373
+ return uploadSourceMaps(apiKey, endpoint, buildHash, maps);
374
+ }
375
+ const checkAvailable = options?.checkBlobAvailable ?? (async () => {
376
+ try {
377
+ await import("@vercel/blob/client");
378
+ return true;
379
+ } catch {
380
+ return false;
381
+ }
382
+ });
383
+ const blobAvailable = await checkAvailable();
384
+ if (blobAvailable) {
385
+ return uploadSourceMapsPresigned(
386
+ apiKey,
387
+ endpoint,
388
+ buildHash,
389
+ maps,
390
+ options?.blobUploader
391
+ );
392
+ }
393
+ sdkLog(
394
+ "warn",
395
+ `[glasstrace] Build exceeds 4.5MB (${totalBytes} bytes). Install @vercel/blob for presigned uploads to avoid serverless body size limits. Falling back to legacy upload.`
396
+ );
397
+ return uploadSourceMaps(apiKey, endpoint, buildHash, maps);
398
+ }
399
+
400
+ export {
401
+ readEnvVars,
402
+ resolveConfig,
403
+ isProductionDisabled,
404
+ isAnonymousMode,
405
+ maybeShowMcpNudge,
406
+ installConsoleCapture,
407
+ collectSourceMaps,
408
+ computeBuildHash,
409
+ uploadSourceMaps,
410
+ PRESIGNED_THRESHOLD_BYTES,
411
+ requestPresignedTokens,
412
+ uploadToBlob,
413
+ submitManifest,
414
+ uploadSourceMapsPresigned,
415
+ uploadSourceMapsAuto
416
+ };
417
+ //# sourceMappingURL=chunk-SLSWEQCC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/source-map-uploader.ts","../src/nudge/error-nudge.ts","../src/env-detection.ts","../src/console-capture.ts"],"sourcesContent":["import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport { execFileSync } from \"node:child_process\";\nimport { sdkLog } from \"./console-capture.js\";\nimport {\n SourceMapUploadResponseSchema,\n type SourceMapUploadResponse,\n PresignedUploadResponseSchema,\n type PresignedUploadResponse,\n SourceMapManifestResponseSchema,\n type SourceMapManifestResponse,\n} from \"@glasstrace/protocol\";\n\nexport interface SourceMapEntry {\n filePath: string;\n content: string;\n}\n\n/**\n * Recursively finds all .map files in the given build directory.\n * Returns relative paths and file contents.\n */\nexport async function collectSourceMaps(\n buildDir: string,\n): Promise<SourceMapEntry[]> {\n const results: SourceMapEntry[] = [];\n\n try {\n await walkDir(buildDir, buildDir, results);\n } catch {\n // Directory doesn't exist or is unreadable — return empty\n return [];\n }\n\n return results;\n}\n\nasync function walkDir(\n baseDir: string,\n currentDir: string,\n results: SourceMapEntry[],\n): Promise<void> {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fs.readdir(currentDir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const fullPath = path.join(currentDir, entry.name);\n\n if (entry.isDirectory()) {\n await walkDir(baseDir, fullPath, results);\n } else if (entry.isFile() && entry.name.endsWith(\".map\")) {\n try {\n const content = await fs.readFile(fullPath, \"utf-8\");\n const relativePath = path.relative(baseDir, fullPath).replace(/\\\\/g, \"/\");\n // Strip the trailing .map extension so the key matches the compiled\n // JS path that the runtime uses for stack-frame lookups (e.g.\n // \"static/chunks/main.js\" instead of \"static/chunks/main.js.map\").\n const compiledPath = relativePath.replace(/\\.map$/, \"\");\n results.push({ filePath: compiledPath, content });\n } catch {\n // Skip unreadable files\n }\n }\n }\n}\n\n/**\n * Computes a build hash for source map uploads.\n *\n * First tries `git rev-parse HEAD` to get the git commit SHA.\n * On failure, falls back to a deterministic content hash:\n * sort source map file paths alphabetically, concatenate each as\n * `{relativePath}\\n{fileLength}\\n{fileContent}`, then SHA-256 the result.\n */\nexport async function computeBuildHash(\n maps?: SourceMapEntry[],\n): Promise<string> {\n // Try git first\n try {\n const sha = execFileSync(\"git\", [\"rev-parse\", \"HEAD\"], { encoding: \"utf-8\" }).trim();\n if (sha) {\n return sha;\n }\n } catch {\n // Git not available, fall through to content hash\n }\n\n // Fallback: content-based hash\n const sortedMaps = [...(maps ?? [])].sort((a, b) =>\n a.filePath.localeCompare(b.filePath),\n );\n\n const hashInput = sortedMaps\n .map((m) => `${m.filePath}\\n${m.content.length}\\n${m.content}`)\n .join(\"\");\n\n const hash = crypto.createHash(\"sha256\").update(hashInput).digest(\"hex\");\n return hash;\n}\n\n/**\n * Uploads source maps to the ingestion API.\n *\n * POSTs to `{endpoint}/v1/source-maps` with the API key, build hash,\n * and file entries. Validates the response against SourceMapUploadResponseSchema.\n */\nexport async function uploadSourceMaps(\n apiKey: string,\n endpoint: string,\n buildHash: string,\n maps: SourceMapEntry[],\n): Promise<SourceMapUploadResponse> {\n const body = {\n apiKey,\n buildHash,\n files: maps.map((m) => ({\n filePath: m.filePath,\n sourceMap: m.content,\n })),\n };\n\n const baseUrl = stripTrailingSlashes(endpoint);\n const response = await fetch(`${baseUrl}/v1/source-maps`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n // Consume the response body to release the connection back to the pool.\n // Without this, the underlying TCP socket stays allocated until GC, which\n // causes connection pool exhaustion under sustained error conditions.\n // Wrapped in try-catch so a stream error doesn't mask the HTTP status error.\n try { await response.text(); } catch { /* body drain is best-effort */ }\n throw new Error(\n `Source map upload failed: ${String(response.status)} ${response.statusText}`,\n );\n }\n\n const json: unknown = await response.json();\n return SourceMapUploadResponseSchema.parse(json);\n}\n\n// ---------------------------------------------------------------------------\n// Presigned source map upload (3-phase flow for large builds)\n// ---------------------------------------------------------------------------\n\n/** Builds at or above this byte size route to the presigned upload flow. */\nexport const PRESIGNED_THRESHOLD_BYTES = 4_500_000;\n\n/** Signature for the blob upload function, injectable for testing. */\nexport type BlobUploader = (\n clientToken: string,\n pathname: string,\n content: string,\n) => Promise<{ url: string; size: number }>;\n\n/**\n * Strips trailing slashes from a URL string.\n * Uses an iterative approach to avoid regex (CodeQL js/polynomial-redos).\n */\nfunction stripTrailingSlashes(url: string): string {\n let result = url;\n while (result.endsWith(\"/\")) {\n result = result.slice(0, -1);\n }\n return result;\n}\n\n/**\n * Phase 1: Request presigned upload tokens from the ingestion API.\n *\n * POSTs to `{endpoint}/v1/source-maps/presign` with the build hash and\n * file metadata. Returns presigned tokens for each file that the client\n * uses to upload directly to blob storage.\n */\nexport async function requestPresignedTokens(\n apiKey: string,\n endpoint: string,\n buildHash: string,\n files: Array<{ filePath: string; sizeBytes: number }>,\n): Promise<PresignedUploadResponse> {\n const baseUrl = stripTrailingSlashes(endpoint);\n const response = await fetch(`${baseUrl}/v1/source-maps/presign`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({ buildHash, files }),\n });\n\n if (!response.ok) {\n try { await response.text(); } catch { /* body drain is best-effort */ }\n throw new Error(\n `Presigned token request failed: ${String(response.status)} ${response.statusText}`,\n );\n }\n\n const json: unknown = await response.json();\n return PresignedUploadResponseSchema.parse(json);\n}\n\n/**\n * Phase 2: Upload a single source map to blob storage using a presigned token.\n *\n * Dynamically imports `@vercel/blob/client` to avoid bundling the dependency.\n * Throws a descriptive error if the package is not installed.\n */\nexport async function uploadToBlob(\n clientToken: string,\n pathname: string,\n content: string,\n): Promise<{ url: string; size: number }> {\n let mod: { put: (pathname: string, body: Blob, options: { access: string; token: string }) => Promise<{ url: string }> };\n try {\n mod = await import(\"@vercel/blob/client\") as typeof mod;\n } catch (err) {\n // Distinguish \"not installed\" from other import errors\n const code = (err as NodeJS.ErrnoException).code;\n if (code === \"ERR_MODULE_NOT_FOUND\" || code === \"MODULE_NOT_FOUND\") {\n throw new Error(\n \"Presigned upload requires @vercel/blob. Install it: npm install @vercel/blob\",\n );\n }\n throw err;\n }\n\n const result = await mod.put(pathname, new Blob([content]), {\n access: \"public\",\n token: clientToken,\n });\n\n return { url: result.url, size: Buffer.byteLength(content, \"utf-8\") };\n}\n\n/**\n * Phase 3: Submit the upload manifest to finalize a presigned upload.\n *\n * POSTs to `{endpoint}/v1/source-maps/manifest` with the upload ID,\n * build hash, and blob URLs for each uploaded file. The backend activates\n * the source maps for stack trace resolution.\n */\nexport async function submitManifest(\n apiKey: string,\n endpoint: string,\n uploadId: string,\n buildHash: string,\n files: Array<{ filePath: string; sizeBytes: number; blobUrl: string }>,\n): Promise<SourceMapManifestResponse> {\n const baseUrl = stripTrailingSlashes(endpoint);\n const response = await fetch(`${baseUrl}/v1/source-maps/manifest`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({ uploadId, buildHash, files }),\n });\n\n if (!response.ok) {\n try { await response.text(); } catch { /* body drain is best-effort */ }\n throw new Error(\n `Source map manifest submission failed: ${String(response.status)} ${response.statusText}`,\n );\n }\n\n const json: unknown = await response.json();\n return SourceMapManifestResponseSchema.parse(json);\n}\n\n/**\n * Orchestrates the 3-phase presigned upload flow.\n *\n * 1. Requests presigned tokens for all source map files\n * 2. Uploads each file to blob storage with a concurrency limit of 5\n * 3. Submits the manifest to finalize the upload\n *\n * Accepts an optional `blobUploader` for test injection; defaults to\n * {@link uploadToBlob}.\n */\nexport async function uploadSourceMapsPresigned(\n apiKey: string,\n endpoint: string,\n buildHash: string,\n maps: SourceMapEntry[],\n blobUploader: BlobUploader = uploadToBlob,\n): Promise<SourceMapManifestResponse> {\n if (maps.length === 0) {\n throw new Error(\"No source maps to upload\");\n }\n\n // Phase 1: request presigned tokens\n const presigned = await requestPresignedTokens(apiKey, endpoint, buildHash,\n maps.map((m) => ({\n filePath: m.filePath,\n sizeBytes: Buffer.byteLength(m.content, \"utf-8\"),\n })),\n );\n\n // Build a lookup map for O(1) access by filePath\n const mapsByPath = new Map(maps.map((m) => [m.filePath, m]));\n\n if (mapsByPath.size !== maps.length) {\n throw new Error(\"Duplicate filePath entries in source maps\");\n }\n\n // Phase 2: upload to blob storage with concurrency limit of 5.\n // Validate all tokens have matching entries before starting any uploads.\n for (const token of presigned.files) {\n if (!mapsByPath.has(token.filePath)) {\n throw new Error(\n `Presigned token for \"${token.filePath}\" has no matching source map entry`,\n );\n }\n }\n\n // Phase 2: upload to blob storage in chunks of CONCURRENCY\n const CONCURRENCY = 5;\n const uploadResults: Array<{ filePath: string; sizeBytes: number; blobUrl: string }> = [];\n\n for (let i = 0; i < presigned.files.length; i += CONCURRENCY) {\n const chunk = presigned.files.slice(i, i + CONCURRENCY);\n const chunkResults = await Promise.all(\n chunk.map(async (token) => {\n const entry = mapsByPath.get(token.filePath)!;\n const result = await blobUploader(token.clientToken, token.pathname, entry.content);\n return {\n filePath: token.filePath,\n sizeBytes: result.size,\n blobUrl: result.url,\n };\n }),\n );\n uploadResults.push(...chunkResults);\n }\n\n // Phase 3: submit manifest\n return submitManifest(apiKey, endpoint, presigned.uploadId, buildHash, uploadResults);\n}\n\n/**\n * Options for {@link uploadSourceMapsAuto}, primarily used for test injection.\n */\nexport interface AutoUploadOptions {\n /** Override blob availability check (for testing). */\n checkBlobAvailable?: () => Promise<boolean>;\n /** Override blob uploader (for testing). */\n blobUploader?: BlobUploader;\n}\n\n/**\n * Automatically routes source map uploads based on total build size.\n *\n * - Below {@link PRESIGNED_THRESHOLD_BYTES}: uses the legacy single-request\n * {@link uploadSourceMaps} endpoint.\n * - At or above the threshold: checks if `@vercel/blob` is available and\n * uses the presigned 3-phase flow. Falls back to legacy with a warning\n * if the package is not installed.\n */\nexport async function uploadSourceMapsAuto(\n apiKey: string,\n endpoint: string,\n buildHash: string,\n maps: SourceMapEntry[],\n options?: AutoUploadOptions,\n): Promise<SourceMapUploadResponse | SourceMapManifestResponse> {\n if (maps.length === 0) {\n throw new Error(\"No source maps to upload\");\n }\n\n const totalBytes = maps.reduce(\n (sum, m) => sum + Buffer.byteLength(m.content, \"utf-8\"),\n 0,\n );\n\n if (totalBytes < PRESIGNED_THRESHOLD_BYTES) {\n return uploadSourceMaps(apiKey, endpoint, buildHash, maps);\n }\n\n // Check if @vercel/blob is available\n const checkAvailable = options?.checkBlobAvailable ?? (async () => {\n try {\n await import(\"@vercel/blob/client\");\n return true;\n } catch {\n return false;\n }\n });\n\n const blobAvailable = await checkAvailable();\n\n if (blobAvailable) {\n return uploadSourceMapsPresigned(\n apiKey, endpoint, buildHash, maps, options?.blobUploader,\n );\n }\n\n // Fall back to legacy upload with a warning\n sdkLog(\"warn\",\n `[glasstrace] Build exceeds 4.5MB (${totalBytes} bytes). Install @vercel/blob for ` +\n `presigned uploads to avoid serverless body size limits. Falling back to legacy upload.`\n );\n\n return uploadSourceMaps(apiKey, endpoint, buildHash, maps);\n}\n","import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { resolveConfig, isProductionDisabled } from \"../env-detection.js\";\n\n/**\n * Module-level flag ensuring the nudge fires at most once per process.\n */\nlet hasFired = false;\n\n/**\n * Strips control characters (except space) from a string to prevent\n * terminal escape sequence injection via error summaries written to stderr.\n */\nfunction sanitize(input: string): string {\n // eslint-disable-next-line no-control-regex\n return input.replace(/[\\x00-\\x1f\\x7f]/g, \"\");\n}\n\n/**\n * Shows a one-time stderr nudge when the SDK captures its first error\n * and the MCP connection marker file is absent.\n *\n * The nudge is suppressed when:\n * - It has already fired in this process\n * - The `.glasstrace/mcp-connected` marker file exists at the project root\n * - The environment is detected as production (and force-enable is off)\n *\n * Uses `process.stderr.write()` instead of `console.error()` to avoid\n * being captured by OpenTelemetry console instrumentation.\n */\nexport function maybeShowMcpNudge(errorSummary: string): void {\n if (hasFired) {\n return;\n }\n\n // Production check — suppress silently, but remember the decision\n // so subsequent calls fast-exit via hasFired without re-running I/O.\n const config = resolveConfig();\n if (isProductionDisabled(config)) {\n hasFired = true;\n return;\n }\n\n // Check for MCP connection marker file.\n // Guard process.cwd() — it throws ENOENT if the working directory has been removed.\n let markerExists = false;\n try {\n const markerPath = join(process.cwd(), \".glasstrace\", \"mcp-connected\");\n markerExists = existsSync(markerPath);\n } catch {\n // Permission denied, ENOENT from cwd(), or other filesystem error — treat as not connected\n markerExists = false;\n }\n\n if (markerExists) {\n hasFired = true;\n return;\n }\n\n // Fire the nudge exactly once\n hasFired = true;\n\n const safe = sanitize(errorSummary);\n process.stderr.write(\n `[glasstrace] Error captured: ${safe}\\n` +\n ` Debug with AI: ask your agent \"What's the latest Glasstrace error?\"\\n` +\n ` Not connected? Run: npx glasstrace mcp add\\n`,\n );\n}\n","import type { GlasstraceEnvVars, GlasstraceOptions } from \"@glasstrace/protocol\";\n\n/**\n * Resolved configuration after merging explicit options with environment variables.\n */\nexport interface ResolvedConfig {\n apiKey: string | undefined;\n endpoint: string;\n forceEnable: boolean;\n verbose: boolean;\n environment: string | undefined;\n coverageMapEnabled: boolean;\n nodeEnv: string | undefined;\n vercelEnv: string | undefined;\n}\n\nconst DEFAULT_ENDPOINT = \"https://api.glasstrace.dev\";\n\n/**\n * Reads all recognized Glasstrace environment variables from process.env.\n * Returns undefined for any variable not set. Never throws.\n */\nexport function readEnvVars(): GlasstraceEnvVars {\n return {\n GLASSTRACE_API_KEY: process.env.GLASSTRACE_API_KEY?.trim() || undefined,\n GLASSTRACE_FORCE_ENABLE: process.env.GLASSTRACE_FORCE_ENABLE,\n GLASSTRACE_ENV: process.env.GLASSTRACE_ENV,\n GLASSTRACE_COVERAGE_MAP: process.env.GLASSTRACE_COVERAGE_MAP,\n NODE_ENV: process.env.NODE_ENV,\n VERCEL_ENV: process.env.VERCEL_ENV,\n };\n}\n\n/**\n * Merges explicit GlasstraceOptions with environment variables.\n * Explicit options take precedence over environment variables.\n */\nexport function resolveConfig(options?: GlasstraceOptions): ResolvedConfig {\n const env = readEnvVars();\n\n return {\n apiKey: options?.apiKey ?? env.GLASSTRACE_API_KEY,\n endpoint: options?.endpoint ?? DEFAULT_ENDPOINT,\n forceEnable: options?.forceEnable ?? env.GLASSTRACE_FORCE_ENABLE === \"true\",\n verbose: options?.verbose ?? false,\n environment: env.GLASSTRACE_ENV,\n coverageMapEnabled: env.GLASSTRACE_COVERAGE_MAP === \"true\",\n nodeEnv: env.NODE_ENV,\n vercelEnv: env.VERCEL_ENV,\n };\n}\n\n/**\n * Returns true when the SDK should be inactive (production detected without force-enable).\n * Logic order:\n * 1. forceEnable === true → return false (override)\n * 2. NODE_ENV === 'production' → return true\n * 3. VERCEL_ENV === 'production' → return true\n * 4. Otherwise → return false\n */\nexport function isProductionDisabled(config: ResolvedConfig): boolean {\n if (config.forceEnable) {\n return false;\n }\n if (config.nodeEnv === \"production\") {\n return true;\n }\n if (config.vercelEnv === \"production\") {\n return true;\n }\n return false;\n}\n\n/**\n * Returns true when no API key is configured (anonymous mode).\n * Treats undefined, empty string, whitespace-only, and gt_anon_* keys as anonymous.\n */\nexport function isAnonymousMode(config: ResolvedConfig): boolean {\n if (config.apiKey === undefined) {\n return true;\n }\n if (config.apiKey.trim() === \"\") {\n return true;\n }\n if (config.apiKey.startsWith(\"gt_anon_\")) {\n return true;\n }\n return false;\n}\n","/**\n * Console error/warn capture module.\n *\n * When enabled, monkey-patches `console.error` and `console.warn` to record\n * their output as OTel span events on the currently active span. SDK-internal\n * log messages (prefixed with \"[glasstrace]\") are never captured.\n */\n\nimport { maybeShowMcpNudge } from \"./nudge/error-nudge.js\";\n\n/**\n * Module-level flag to suppress capture of SDK-internal log messages.\n * Set to `true` before calling `console.warn`/`console.error` from SDK code,\n * then reset to `false` immediately after.\n */\nexport let isGlasstraceLog = false;\n\n/** Saved reference to the original `console.error`. */\nlet originalError: typeof console.error | null = null;\n\n/** Saved reference to the original `console.warn`. */\nlet originalWarn: typeof console.warn | null = null;\n\n/** Whether the console capture is currently installed. */\nlet installed = false;\n\n/** Cached OTel API module reference, resolved at install time. */\nlet otelApi: typeof import(\"@opentelemetry/api\") | null = null;\n\n/**\n * Formats console arguments into a single string for span event attributes.\n * Uses best-effort serialization: strings pass through, Errors preserve their\n * stack trace, and other values are JSON-stringified with a String() fallback.\n */\nfunction formatArgs(args: unknown[]): string {\n return args\n .map((arg) => {\n if (typeof arg === \"string\") return arg;\n if (arg instanceof Error) return arg.stack ?? arg.message;\n try {\n return JSON.stringify(arg);\n } catch {\n return String(arg);\n }\n })\n .join(\" \");\n}\n\n/**\n * Returns `true` if the first argument is a string starting with \"[glasstrace]\".\n * Used to skip capture of SDK-internal log messages without requiring every\n * call site to set the `isGlasstraceLog` flag.\n */\nfunction isSdkMessage(args: unknown[]): boolean {\n return typeof args[0] === \"string\" && args[0].startsWith(\"[glasstrace]\");\n}\n\n/**\n * Installs console capture by replacing `console.error` and `console.warn`\n * with wrappers that record span events on the active OTel span.\n *\n * Must be called after OTel is configured so the API module is available.\n * Safe to call multiple times; subsequent calls are no-ops.\n */\nexport async function installConsoleCapture(): Promise<void> {\n if (installed) return;\n\n // Resolve OTel API at install time via dynamic import so that:\n // 1. tsup inlines @opentelemetry/api into the bundle (it's in noExternal)\n // 2. vitest's vi.doMock can intercept this import for testing\n try {\n otelApi = await import(\"@opentelemetry/api\");\n } catch {\n otelApi = null;\n }\n\n originalError = console.error;\n originalWarn = console.warn;\n installed = true;\n\n console.error = (...args: unknown[]) => {\n // Always call the original first to preserve developer experience\n originalError!.apply(console, args);\n\n // Skip SDK-internal messages and flagged messages\n if (isGlasstraceLog || isSdkMessage(args)) return;\n\n if (otelApi) {\n const span = otelApi.trace.getSpan(otelApi.context.active());\n if (span) {\n const formattedMessage = formatArgs(args);\n span.addEvent(\"console.error\", {\n \"console.message\": formattedMessage,\n });\n // Show one-time MCP connection nudge on first captured error\n try {\n maybeShowMcpNudge(formattedMessage);\n } catch {\n // Never allow the monkey-patched console.error wrapper to throw\n }\n }\n }\n };\n\n console.warn = (...args: unknown[]) => {\n originalWarn!.apply(console, args);\n\n if (isGlasstraceLog || isSdkMessage(args)) return;\n\n if (otelApi) {\n const span = otelApi.trace.getSpan(otelApi.context.active());\n if (span) {\n span.addEvent(\"console.warn\", {\n \"console.message\": formatArgs(args),\n });\n }\n }\n };\n}\n\n/**\n * Restores the original `console.error` and `console.warn` methods.\n * Primarily intended for use in tests.\n */\nexport function uninstallConsoleCapture(): void {\n if (!installed) return;\n\n if (originalError) console.error = originalError;\n if (originalWarn) console.warn = originalWarn;\n\n originalError = null;\n originalWarn = null;\n otelApi = null;\n installed = false;\n}\n\n/**\n * Logs a message from SDK-internal code without triggering console capture.\n *\n * Use this helper in new SDK code instead of bare `console.warn(...)` calls\n * to prevent SDK log messages from being recorded as user-facing span events.\n *\n * @param level - The console log level to use.\n * @param message - The message to log.\n */\nexport function sdkLog(level: \"warn\" | \"info\" | \"error\", message: string): void {\n isGlasstraceLog = true;\n try {\n console[level](message);\n } finally {\n isGlasstraceLog = false;\n }\n}\n"],"mappings":"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","names":[]}
package/dist/cli/init.cjs CHANGED
@@ -14792,7 +14792,9 @@ var init_dist = __esm({
14792
14792
  });
14793
14793
  DiscoveryResponseSchema = external_exports.object({
14794
14794
  key: AnonApiKeySchema,
14795
- sessionId: SessionIdSchema
14795
+ sessionId: SessionIdSchema,
14796
+ claimed: external_exports.boolean().optional(),
14797
+ accountHint: external_exports.string().optional()
14796
14798
  });
14797
14799
  SourceMapUploadResponseSchema = external_exports.object({
14798
14800
  success: external_exports.literal(true),
@@ -15658,6 +15660,7 @@ var init_mcp_add = __esm({
15658
15660
  // src/cli/uninit.ts
15659
15661
  var uninit_exports = {};
15660
15662
  __export(uninit_exports, {
15663
+ findMatchingDelimiter: () => findMatchingDelimiter,
15661
15664
  findMatchingParen: () => findMatchingParen,
15662
15665
  isInitCreatedInstrumentation: () => isInitCreatedInstrumentation,
15663
15666
  processJsonMcpConfig: () => processJsonMcpConfig,
@@ -15666,23 +15669,64 @@ __export(uninit_exports, {
15666
15669
  removeMarkerSection: () => removeMarkerSection,
15667
15670
  removeRegisterGlasstrace: () => removeRegisterGlasstrace,
15668
15671
  runUninit: () => runUninit,
15672
+ skipString: () => skipString,
15669
15673
  unwrapCJSExport: () => unwrapCJSExport,
15670
15674
  unwrapExport: () => unwrapExport
15671
15675
  });
15672
- function findMatchingParen(text, openPos) {
15676
+ function skipString(text, start, quote) {
15677
+ let i = start + 1;
15678
+ while (i < text.length) {
15679
+ if (text[i] === "\\") {
15680
+ i += 2;
15681
+ continue;
15682
+ }
15683
+ if (text[i] === quote) {
15684
+ return i + 1;
15685
+ }
15686
+ i++;
15687
+ }
15688
+ return text.length;
15689
+ }
15690
+ function findMatchingDelimiter(text, openPos, openChar, closeChar) {
15673
15691
  let depth = 0;
15674
- for (let i = openPos; i < text.length; i++) {
15675
- if (text[i] === "(") {
15692
+ let i = openPos;
15693
+ while (i < text.length) {
15694
+ const ch = text[i];
15695
+ if (ch === '"' || ch === "'" || ch === "`") {
15696
+ i = skipString(text, i, ch);
15697
+ continue;
15698
+ }
15699
+ if (ch === "/" && text[i + 1] === "/") {
15700
+ const newline = text.indexOf("\n", i);
15701
+ if (newline === -1) {
15702
+ return -1;
15703
+ }
15704
+ i = newline + 1;
15705
+ continue;
15706
+ }
15707
+ if (ch === "/" && text[i + 1] === "*") {
15708
+ const end = text.indexOf("*/", i + 2);
15709
+ if (end === -1) {
15710
+ return -1;
15711
+ }
15712
+ i = end + 2;
15713
+ continue;
15714
+ }
15715
+ if (ch === openChar) {
15676
15716
  depth++;
15677
- } else if (text[i] === ")") {
15717
+ } else if (ch === closeChar) {
15678
15718
  depth--;
15679
15719
  if (depth === 0) {
15680
15720
  return i;
15681
15721
  }
15682
15722
  }
15723
+ i++;
15683
15724
  }
15684
15725
  return -1;
15685
15726
  }
15727
+ function findMatchingParen(text, openPos) {
15728
+ return findMatchingDelimiter(text, openPos, "(", ")");
15729
+ }
15686
15730
  function unwrapExport(content) {
15687
15731
  const pattern = /export\s+default\s+withGlasstraceConfig\s*\(/;
15688
15732
  const match = pattern.exec(content);
@@ -15813,18 +15857,7 @@ function isInitCreatedInstrumentation(content) {
15813
15857
  return topLevelBefore.length === 0 && topLevelAfter.length === 0;
15814
15858
  }
15815
15859
  function findMatchingBrace(text, openPos) {
15816
- let depth = 0;
15817
- for (let i = openPos; i < text.length; i++) {
15818
- if (text[i] === "{") {
15819
- depth++;
15820
- } else if (text[i] === "}") {
15821
- depth--;
15822
- if (depth === 0) {
15823
- return i;
15824
- }
15825
- }
15826
- }
15827
- return -1;
15860
+ return findMatchingDelimiter(text, openPos, "{", "}");
15828
15861
  }
15829
15862
  function removeRegisterGlasstrace(content) {
15830
15863
  let result = content;
@@ -16134,16 +16167,22 @@ async function runUninit(options) {
16134
16167
  if (fs5.existsSync(windsurfConfigPath)) {
16135
16168
  const content = fs5.readFileSync(windsurfConfigPath, "utf-8");
16136
16169
  const windsurfResult = processJsonMcpConfig(content);
16170
+ const home = os.homedir();
16171
+ const displayPath = windsurfConfigPath.startsWith(home) ? "~" + windsurfConfigPath.slice(home.length) : windsurfConfigPath;
16137
16172
  if (windsurfResult.action === "deleted") {
16138
16173
  if (!dryRun) {
16139
16174
  fs5.unlinkSync(windsurfConfigPath);
16140
16175
  }
16141
- summary.push(`${prefix}Deleted Windsurf MCP config`);
16176
+ summary.push(
16177
+ `${prefix}Deleted global Windsurf config (${displayPath})`
16178
+ );
16142
16179
  } else if (windsurfResult.action === "removed-key" && windsurfResult.content !== void 0) {
16143
16180
  if (!dryRun) {
16144
16181
  fs5.writeFileSync(windsurfConfigPath, windsurfResult.content, "utf-8");
16145
16182
  }
16146
- summary.push(`${prefix}Removed glasstrace from Windsurf MCP config`);
16183
+ summary.push(
16184
+ `${prefix}Removed glasstrace from global Windsurf config (${displayPath})`
16185
+ );
16147
16186
  }
16148
16187
  }
16149
16188
  }