@glasstrace/sdk 1.1.0 → 1.1.2

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.
Files changed (66) hide show
  1. package/README.md +78 -1
  2. package/dist/{chunk-55FBXXER.js → chunk-2M57EO6U.js} +2 -2
  3. package/dist/chunk-3LILTM3T.js +384 -0
  4. package/dist/chunk-3LILTM3T.js.map +1 -0
  5. package/dist/{chunk-KE7MCPO5.js → chunk-4EZ6JTDG.js} +2 -2
  6. package/dist/{chunk-67RIOAXV.js → chunk-6RNBUUBR.js} +2 -2
  7. package/dist/{chunk-DO2YPMQ5.js → chunk-C567H5EQ.js} +23 -5
  8. package/dist/chunk-C567H5EQ.js.map +1 -0
  9. package/dist/{chunk-UGJ3X4CT.js → chunk-DST4UBXU.js} +2 -2
  10. package/dist/{chunk-DXRZKKSO.js → chunk-NB7GJE4S.js} +2 -4
  11. package/dist/chunk-NB7GJE4S.js.map +1 -0
  12. package/dist/{chunk-HAU66QBQ.js → chunk-P4OYPFQ5.js} +9 -9
  13. package/dist/chunk-P4OYPFQ5.js.map +1 -0
  14. package/dist/{chunk-LU3PPAOQ.js → chunk-UJ2JC7PZ.js} +4 -4
  15. package/dist/chunk-UJ2JC7PZ.js.map +1 -0
  16. package/dist/{chunk-TQ54WLCZ.js → chunk-X5MAXP5T.js} +2 -1
  17. package/dist/{chunk-ZBTC5QIQ.js → chunk-Z35HKVSO.js} +137 -10
  18. package/dist/chunk-Z35HKVSO.js.map +1 -0
  19. package/dist/cli/init.cjs +1313 -1118
  20. package/dist/cli/init.cjs.map +1 -1
  21. package/dist/cli/init.js +456 -72
  22. package/dist/cli/init.js.map +1 -1
  23. package/dist/cli/mcp-add.cjs +257 -85
  24. package/dist/cli/mcp-add.cjs.map +1 -1
  25. package/dist/cli/mcp-add.d.cts +35 -4
  26. package/dist/cli/mcp-add.d.ts +35 -4
  27. package/dist/cli/mcp-add.js +61 -25
  28. package/dist/cli/mcp-add.js.map +1 -1
  29. package/dist/cli/status.cjs.map +1 -1
  30. package/dist/cli/status.js +1 -1
  31. package/dist/cli/uninit.cjs +1 -1
  32. package/dist/cli/uninit.cjs.map +1 -1
  33. package/dist/cli/uninit.js +4 -4
  34. package/dist/cli/validate.cjs +14162 -2
  35. package/dist/cli/validate.cjs.map +1 -1
  36. package/dist/cli/validate.d.cts +7 -3
  37. package/dist/cli/validate.d.ts +7 -3
  38. package/dist/cli/validate.js +25 -2
  39. package/dist/cli/validate.js.map +1 -1
  40. package/dist/edge-entry.js +2 -2
  41. package/dist/index.cjs +423 -12
  42. package/dist/index.cjs.map +1 -1
  43. package/dist/index.js +5 -5
  44. package/dist/{monorepo-N5Z63XP7.js → monorepo-PFVNPQ6X.js} +3 -3
  45. package/dist/node-entry.cjs +423 -12
  46. package/dist/node-entry.cjs.map +1 -1
  47. package/dist/node-entry.js +7 -7
  48. package/dist/node-subpath.js +3 -3
  49. package/dist/{source-map-uploader-BJIXRLJ6.js → source-map-uploader-DPUUCLNW.js} +3 -3
  50. package/package.json +1 -1
  51. package/dist/chunk-DO2YPMQ5.js.map +0 -1
  52. package/dist/chunk-DXRZKKSO.js.map +0 -1
  53. package/dist/chunk-HAU66QBQ.js.map +0 -1
  54. package/dist/chunk-IP4NMDJK.js +0 -98
  55. package/dist/chunk-IP4NMDJK.js.map +0 -1
  56. package/dist/chunk-LU3PPAOQ.js.map +0 -1
  57. package/dist/chunk-O63DJKIJ.js +0 -460
  58. package/dist/chunk-O63DJKIJ.js.map +0 -1
  59. package/dist/chunk-ZBTC5QIQ.js.map +0 -1
  60. /package/dist/{chunk-55FBXXER.js.map → chunk-2M57EO6U.js.map} +0 -0
  61. /package/dist/{chunk-KE7MCPO5.js.map → chunk-4EZ6JTDG.js.map} +0 -0
  62. /package/dist/{chunk-67RIOAXV.js.map → chunk-6RNBUUBR.js.map} +0 -0
  63. /package/dist/{chunk-UGJ3X4CT.js.map → chunk-DST4UBXU.js.map} +0 -0
  64. /package/dist/{chunk-TQ54WLCZ.js.map → chunk-X5MAXP5T.js.map} +0 -0
  65. /package/dist/{monorepo-N5Z63XP7.js.map → monorepo-PFVNPQ6X.js.map} +0 -0
  66. /package/dist/{source-map-uploader-BJIXRLJ6.js.map → source-map-uploader-DPUUCLNW.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/uninit.ts","../src/cli/discovery-file.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { NEXT_CONFIG_NAMES } from \"./constants.js\";\nimport { readEnvLocalApiKey, isDevApiKey } from \"../mcp-runtime.js\";\nimport {\n removeDiscoveryFile,\n relativeDiscoveryPath,\n} from \"./discovery-file.js\";\n\n/**\n * Options for the uninit command.\n */\nexport interface UninitOptions {\n projectRoot: string;\n dryRun: boolean;\n /**\n * When true, skip interactive confirmation before destructive actions\n * such as removing a claimed developer API key from `.env.local`\n * (DISC-1247 Scenario 6).\n */\n force?: boolean;\n /**\n * Optional prompt callback; when omitted, uninit uses a TTY-based\n * `readline` prompt in interactive mode and defaults to `false`\n * (abort) when no TTY is attached. Exposed for testing.\n */\n prompt?: (question: string, defaultValue: boolean) => Promise<boolean>;\n}\n\n/**\n * Result of running the uninit command.\n */\nexport interface UninitResult {\n exitCode: number;\n summary: string[];\n warnings: string[];\n errors: string[];\n}\n\n/**\n * MCP config files that init may create.\n * These are JSON files containing `mcpServers.glasstrace`.\n */\nconst MCP_CONFIG_FILES = [\".mcp.json\", \".cursor/mcp.json\", \".gemini/settings.json\"] as const;\n\n/**\n * Agent info files that may contain glasstrace marker sections.\n * Both HTML-style (`<!-- glasstrace:mcp:start -->`) and hash-style\n * (`# glasstrace:mcp:start`) markers are supported.\n */\nconst AGENT_INFO_FILES = [\n \"CLAUDE.md\",\n \"codex.md\",\n \".cursorrules\",\n] as const;\n\n/**\n * Advances past a string literal (double-quoted, single-quoted, or template\n * literal), respecting backslash escapes.\n *\n * Note: Template literals with `${...}` interpolations containing nested\n * backticks are not fully supported — the scanner stops at the first\n * unescaped backtick. This is acceptable because config files (the primary\n * use case for `findMatchingParen`/`findMatchingBrace`) do not use nested\n * template literals.\n *\n * @param text - The source text.\n * @param start - The index of the opening quote character.\n * @param quote - The quote character (`\"`, `'`, or `` ` ``).\n * @returns The index immediately after the closing quote.\n * @internal Exported for unit testing only.\n */\nexport function skipString(text: string, start: number, quote: string): number {\n let i = start + 1;\n while (i < text.length) {\n if (text[i] === \"\\\\\") {\n i += 2;\n continue;\n }\n if (text[i] === quote) {\n return i + 1;\n }\n i++;\n }\n return text.length;\n}\n\n/**\n * Finds the matching closing delimiter for an opening delimiter at the given\n * position, accounting for nesting and skipping delimiters that appear inside\n * string literals (`\"`, `'`, `` ` ``), single-line comments (`//`), and block\n * comments.\n *\n * @param text - The source text to search.\n * @param openPos - The index of the opening delimiter.\n * @param openChar - The opening delimiter character (e.g., `(` or `{`).\n * @param closeChar - The closing delimiter character (e.g., `)` or `}`).\n * @returns The index of the matching closing delimiter, or -1 if not found.\n * @internal Exported for unit testing only.\n */\nexport function findMatchingDelimiter(\n text: string,\n openPos: number,\n openChar: string,\n closeChar: string,\n): number {\n let depth = 0;\n let i = openPos;\n while (i < text.length) {\n const ch = text[i];\n\n // Skip string literals\n if (ch === '\"' || ch === \"'\" || ch === \"`\") {\n i = skipString(text, i, ch);\n continue;\n }\n\n // Skip single-line comments.\n // Note: This may misidentify regex literals containing `//` (e.g.,\n // `/api\\//`). Config files — the primary use case — do not contain\n // regex literals, so this trade-off is acceptable.\n if (ch === \"/\" && text[i + 1] === \"/\") {\n const newline = text.indexOf(\"\\n\", i);\n if (newline === -1) {\n return -1;\n }\n i = newline + 1;\n continue;\n }\n\n // Skip block comments\n if (ch === \"/\" && text[i + 1] === \"*\") {\n const end = text.indexOf(\"*/\", i + 2);\n if (end === -1) {\n return -1;\n }\n i = end + 2;\n continue;\n }\n\n if (ch === openChar) {\n depth++;\n } else if (ch === closeChar) {\n depth--;\n if (depth === 0) {\n return i;\n }\n }\n i++;\n }\n return -1;\n}\n\n/**\n * Finds the matching closing parenthesis for an opening paren at the given\n * position, accounting for nested parentheses and skipping delimiters inside\n * string literals and comments.\n *\n * @param text - The source text to search.\n * @param openPos - The index of the opening `(`.\n * @returns The index of the matching `)`, or -1 if not found.\n * @internal Exported for unit testing only.\n */\nexport function findMatchingParen(text: string, openPos: number): number {\n return findMatchingDelimiter(text, openPos, \"(\", \")\");\n}\n\n/**\n * Removes the `withGlasstraceConfig(...)` wrapper from an ESM default export,\n * restoring the inner expression.\n *\n * Before: `export default withGlasstraceConfig(innerExpr);`\n * After: `export default innerExpr;`\n *\n * @internal Exported for unit testing only.\n */\nexport function unwrapExport(content: string): { content: string; unwrapped: boolean } {\n const pattern = /export\\s+default\\s+withGlasstraceConfig\\s*\\(/;\n const match = pattern.exec(content);\n if (!match) {\n return { content, unwrapped: false };\n }\n\n // Find the opening paren of withGlasstraceConfig(\n const openParenIdx = match.index + match[0].length - 1;\n const closeParenIdx = findMatchingParen(content, openParenIdx);\n if (closeParenIdx === -1) {\n return { content, unwrapped: false };\n }\n\n const innerExpr = content.slice(openParenIdx + 1, closeParenIdx).trim();\n if (innerExpr.length === 0) {\n return { content, unwrapped: false };\n }\n\n // Everything before `export default ...`\n const before = content.slice(0, match.index);\n // Everything after the closing `)` (skip optional semicolon and trailing whitespace)\n const afterClose = content.slice(closeParenIdx + 1);\n const trailing = afterClose.replace(/^;?\\s*/, \"\");\n\n const result = before + `export default ${innerExpr};\\n` + trailing;\n\n return { content: result, unwrapped: true };\n}\n\n/**\n * Removes the `withGlasstraceConfig(...)` wrapper from a CJS module.exports,\n * restoring the inner expression.\n *\n * Before: `module.exports = withGlasstraceConfig(innerExpr);`\n * After: `module.exports = innerExpr;`\n *\n * @internal Exported for unit testing only.\n */\nexport function unwrapCJSExport(content: string): { content: string; unwrapped: boolean } {\n const pattern = /module\\.exports\\s*=\\s*withGlasstraceConfig\\s*\\(/;\n const match = pattern.exec(content);\n if (!match) {\n return { content, unwrapped: false };\n }\n\n const openParenIdx = match.index + match[0].length - 1;\n const closeParenIdx = findMatchingParen(content, openParenIdx);\n if (closeParenIdx === -1) {\n return { content, unwrapped: false };\n }\n\n const innerExpr = content.slice(openParenIdx + 1, closeParenIdx).trim();\n if (innerExpr.length === 0) {\n return { content, unwrapped: false };\n }\n\n const before = content.slice(0, match.index);\n const afterClose = content.slice(closeParenIdx + 1);\n const trailing = afterClose.replace(/^;?\\s*/, \"\");\n\n const result = before + `module.exports = ${innerExpr};\\n` + trailing;\n\n return { content: result, unwrapped: true };\n}\n\n/**\n * Removes the `import { withGlasstraceConfig } from \"@glasstrace/sdk\"` line\n * from file content. If `withGlasstraceConfig` is the only imported specifier,\n * the entire import line is removed. If other specifiers exist, only\n * `withGlasstraceConfig` is removed from the specifier list.\n *\n * @internal Exported for unit testing only.\n */\nexport function removeGlasstraceConfigImport(content: string): string {\n // ESM: import { withGlasstraceConfig } from \"@glasstrace/sdk\"\n const esmSoleImport =\n /import\\s*\\{\\s*withGlasstraceConfig\\s*\\}\\s*from\\s*[\"']@glasstrace\\/sdk[\"']\\s*;?\\s*\\n?/;\n if (esmSoleImport.test(content)) {\n return content.replace(esmSoleImport, \"\");\n }\n\n // ESM with multiple specifiers — remove withGlasstraceConfig from the list\n const esmMultiImport =\n /import\\s*\\{([^}]*)\\}\\s*from\\s*[\"']@glasstrace\\/sdk[\"']/;\n const multiMatch = esmMultiImport.exec(content);\n if (multiMatch) {\n const specifiers = multiMatch[1]\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s !== \"\" && s !== \"withGlasstraceConfig\");\n if (specifiers.length === 0) {\n // All specifiers were withGlasstraceConfig — remove entire import\n return content.replace(\n /import\\s*\\{[^}]*\\}\\s*from\\s*[\"']@glasstrace\\/sdk[\"']\\s*;?\\s*\\n?/,\n \"\",\n );\n }\n const newImport = `import { ${specifiers.join(\", \")} } from \"@glasstrace/sdk\"`;\n return content.replace(multiMatch[0], newImport);\n }\n\n // CJS: const { withGlasstraceConfig } = require(\"@glasstrace/sdk\")\n const cjsSoleRequire =\n /const\\s*\\{\\s*withGlasstraceConfig\\s*\\}\\s*=\\s*require\\s*\\(\\s*[\"']@glasstrace\\/sdk[\"']\\s*\\)\\s*;?\\s*\\n?/;\n if (cjsSoleRequire.test(content)) {\n return content.replace(cjsSoleRequire, \"\");\n }\n\n // CJS with multiple specifiers\n const cjsMultiRequire =\n /const\\s*\\{([^}]*)\\}\\s*=\\s*require\\s*\\(\\s*[\"']@glasstrace\\/sdk[\"']\\s*\\)/;\n const cjsMultiMatch = cjsMultiRequire.exec(content);\n if (cjsMultiMatch) {\n const specifiers = cjsMultiMatch[1]\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s !== \"\" && s !== \"withGlasstraceConfig\");\n if (specifiers.length === 0) {\n return content.replace(\n /const\\s*\\{[^}]*\\}\\s*=\\s*require\\s*\\(\\s*[\"']@glasstrace\\/sdk[\"']\\s*\\)\\s*;?\\s*\\n?/,\n \"\",\n );\n }\n const newRequire = `const { ${specifiers.join(\", \")} } = require(\"@glasstrace/sdk\")`;\n return content.replace(cjsMultiMatch[0], newRequire);\n }\n\n return content;\n}\n\n/**\n * Removes blank lines that appear consecutively (more than one empty line\n * in a row) at the top of a file, which can occur after removing import lines.\n */\nfunction cleanLeadingBlankLines(content: string): string {\n return content.replace(/^\\n{2,}/, \"\\n\");\n}\n\n/**\n * Determines whether an instrumentation.ts file was created by `glasstrace init`\n * (i.e., contains only the standard template with no user-added code).\n *\n * A file is considered init-created if:\n * - The only import from any package is `@glasstrace/sdk`\n * - The only meaningful statement in `register()` is `registerGlasstrace()`\n * - There are no other top-level statements, exports, or declarations outside\n * the register function (prevents deleting files where users added their own code)\n *\n * @internal Exported for unit testing only.\n */\nexport function isInitCreatedInstrumentation(content: string): boolean {\n const lines = content.split(\"\\n\");\n\n // Check that all imports are from @glasstrace/sdk\n const importLines = lines.filter(\n (l) => /^\\s*import\\s/.test(l) && !l.trim().startsWith(\"//\"),\n );\n const nonGlasstraceImports = importLines.filter(\n (l) => !l.includes(\"@glasstrace/sdk\"),\n );\n if (nonGlasstraceImports.length > 0) {\n return false;\n }\n\n // Check that the register() function body only contains registerGlasstrace()\n // and comments — no other meaningful statements\n const registerFnRegex = /export\\s+(?:async\\s+)?function\\s+register\\s*\\([^)]*\\)\\s*\\{/;\n const match = registerFnRegex.exec(content);\n if (!match) {\n // No register function — not a standard init template\n return false;\n }\n\n // Extract the function body\n const afterBrace = content.slice(match.index + match[0].length);\n const closingBraceIdx = findMatchingBrace(content, match.index + match[0].length - 1);\n if (closingBraceIdx === -1) {\n return false;\n }\n\n const body = afterBrace.slice(0, closingBraceIdx - (match.index + match[0].length));\n const bodyLines = body.split(\"\\n\");\n\n // Filter out comments and blank lines — only meaningful statements remain\n const statements = bodyLines.filter((l) => {\n const trimmed = l.trim();\n return trimmed !== \"\" && !trimmed.startsWith(\"//\");\n });\n\n // The only statement should be registerGlasstrace()\n if (statements.length !== 1) {\n return false;\n }\n if (!/^\\s*registerGlasstrace\\s*\\(\\s*\\)\\s*;?\\s*$/.test(statements[0])) {\n return false;\n }\n\n // Verify no other top-level code exists outside imports and the register function.\n // Extract everything that isn't an import line or inside the register() function.\n const beforeFn = content.slice(0, match.index);\n const afterFn = content.slice(closingBraceIdx + 1);\n\n const topLevelBefore = beforeFn.split(\"\\n\").filter((l) => {\n const trimmed = l.trim();\n return (\n trimmed !== \"\" &&\n !trimmed.startsWith(\"//\") &&\n !trimmed.startsWith(\"import \") &&\n !trimmed.startsWith(\"import{\")\n );\n });\n\n const topLevelAfter = afterFn.split(\"\\n\").filter((l) => {\n const trimmed = l.trim();\n return trimmed !== \"\" && !trimmed.startsWith(\"//\");\n });\n\n return topLevelBefore.length === 0 && topLevelAfter.length === 0;\n}\n\n/**\n * Finds the matching closing brace for an opening brace at the given position,\n * skipping delimiters inside string literals and comments.\n */\nfunction findMatchingBrace(text: string, openPos: number): number {\n return findMatchingDelimiter(text, openPos, \"{\", \"}\");\n}\n\n/**\n * Removes the `registerGlasstrace()` call and its `@glasstrace/sdk` import\n * from an instrumentation.ts file, preserving all other code.\n *\n * @internal Exported for unit testing only.\n */\nexport function removeRegisterGlasstrace(content: string): string {\n let result = content;\n\n // Remove all comment-block + registerGlasstrace() call pairs.\n // The init template creates a multi-line comment block before the call:\n // // Glasstrace must be registered before Prisma instrumentation\n // // to ensure all ORM spans are captured correctly.\n // // If you use @prisma/instrumentation, import it after this call.\n // registerGlasstrace();\n // Use global flag to handle multiple occurrences.\n result = result.replace(\n /[ \\t]*\\/\\/\\s*Glasstrace must be registered[^\\n]*\\n(?:[ \\t]*\\/\\/[^\\n]*\\n)*[ \\t]*registerGlasstrace\\s*\\(\\s*\\)\\s*;?\\s*\\n?/g,\n \"\",\n );\n\n // Remove any remaining standalone registerGlasstrace() calls (global)\n result = result.replace(\n /[ \\t]*registerGlasstrace\\s*\\(\\s*\\)\\s*;?\\s*\\n?/g,\n \"\",\n );\n\n // Remove the import line for registerGlasstrace from @glasstrace/sdk\n // If it's the sole import, remove the whole line\n const soleImportPattern =\n /import\\s*\\{\\s*registerGlasstrace\\s*\\}\\s*from\\s*[\"']@glasstrace\\/sdk[\"']\\s*;?\\s*\\n?/;\n if (soleImportPattern.test(result)) {\n result = result.replace(soleImportPattern, \"\");\n } else {\n // Multiple specifiers — remove only registerGlasstrace\n const multiImportPattern =\n /import\\s*\\{([^}]*)\\}\\s*from\\s*[\"']@glasstrace\\/sdk[\"']/;\n const multiMatch = multiImportPattern.exec(result);\n if (multiMatch) {\n const specifiers = multiMatch[1]\n .split(\",\")\n .map((s) => s.trim())\n .filter((s) => s !== \"\" && s !== \"registerGlasstrace\");\n if (specifiers.length === 0) {\n result = result.replace(\n /import\\s*\\{[^}]*\\}\\s*from\\s*[\"']@glasstrace\\/sdk[\"']\\s*;?\\s*\\n?/,\n \"\",\n );\n } else {\n const newImport = `import { ${specifiers.join(\", \")} } from \"@glasstrace/sdk\"`;\n result = result.replace(multiMatch[0], newImport);\n }\n }\n }\n\n return cleanLeadingBlankLines(result);\n}\n\n/**\n * Removes content between glasstrace marker comments from a file.\n * Supports both HTML markers (`<!-- glasstrace:mcp:start/end -->`) and\n * hash markers (`# glasstrace:mcp:start/end`).\n *\n * @internal Exported for unit testing only.\n */\nexport function removeMarkerSection(content: string): { content: string; removed: boolean } {\n const lines = content.split(\"\\n\");\n let startIdx = -1;\n let endIdx = -1;\n\n for (let i = 0; i < lines.length; i++) {\n const trimmed = lines[i].trim();\n if (\n trimmed === \"<!-- glasstrace:mcp:start -->\" ||\n trimmed === \"# glasstrace:mcp:start\"\n ) {\n startIdx = i;\n } else if (\n (trimmed === \"<!-- glasstrace:mcp:end -->\" ||\n trimmed === \"# glasstrace:mcp:end\") &&\n startIdx !== -1\n ) {\n endIdx = i;\n break;\n }\n }\n\n if (startIdx === -1 || endIdx === -1) {\n return { content, removed: false };\n }\n\n const before = lines.slice(0, startIdx);\n const after = lines.slice(endIdx + 1);\n\n // Remove trailing blank line that may have preceded the marker block\n while (before.length > 0 && before[before.length - 1].trim() === \"\") {\n before.pop();\n }\n\n const result = [...before, ...after].join(\"\\n\");\n // Ensure file ends with newline if it has content\n const trimmedResult = result.trimEnd();\n return {\n content: trimmedResult.length > 0 ? trimmedResult + \"\\n\" : \"\",\n removed: true,\n };\n}\n\n/**\n * Removes the `glasstrace` key from an MCP config JSON file's `mcpServers`\n * object. Only deletes the file when `mcpServers` is the sole top-level key\n * and `glasstrace` is the only server entry. When other top-level keys exist\n * (e.g., `$schema`, metadata), the `mcpServers` key is removed (if empty)\n * and the file is preserved.\n *\n * @returns `\"removed-key\"` if the key was removed (other data remains),\n * `\"deleted\"` if the file should be deleted (no other data),\n * or `\"skipped\"` if no glasstrace config was found.\n * @internal Exported for unit testing only.\n */\nexport function processJsonMcpConfig(content: string): {\n action: \"removed-key\" | \"deleted\" | \"skipped\";\n content?: string;\n} {\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(content) as Record<string, unknown>;\n } catch {\n return { action: \"skipped\" };\n }\n\n const mcpServers = parsed[\"mcpServers\"] as Record<string, unknown> | undefined;\n if (!mcpServers || typeof mcpServers !== \"object\" || !(\"glasstrace\" in mcpServers)) {\n return { action: \"skipped\" };\n }\n\n const remainingServers = Object.keys(mcpServers).filter((k) => k !== \"glasstrace\");\n const otherTopLevelKeys = Object.keys(parsed).filter((k) => k !== \"mcpServers\");\n\n if (remainingServers.length === 0 && otherTopLevelKeys.length === 0) {\n // mcpServers.glasstrace is the only data in the file — safe to delete\n return { action: \"deleted\" };\n }\n\n // Remove the glasstrace key, keep other servers\n const { glasstrace: _, ...rest } = mcpServers;\n // Suppress unused variable lint — the destructuring intentionally discards glasstrace\n void _;\n\n if (remainingServers.length > 0) {\n // Other servers remain — keep mcpServers with glasstrace removed\n parsed[\"mcpServers\"] = rest;\n } else {\n // No servers remain but other top-level keys exist — remove mcpServers entirely\n delete parsed[\"mcpServers\"];\n }\n\n return { action: \"removed-key\", content: JSON.stringify(parsed, null, 2) + \"\\n\" };\n}\n\n/**\n * Removes the `[mcp_servers.glasstrace]` section from a TOML config file.\n * Since TOML parsing without a dependency is complex, this uses a line-based\n * approach that handles the standard format written by init.\n *\n * @returns `\"removed-section\"` if the glasstrace section was removed,\n * `\"deleted\"` if the entire file should be deleted (only contained\n * glasstrace config), or `\"skipped\"` if no glasstrace config found.\n * @internal Exported for unit testing only.\n */\nexport function processTomlMcpConfig(content: string): {\n action: \"removed-section\" | \"deleted\" | \"skipped\";\n content?: string;\n} {\n if (!content.includes(\"[mcp_servers.glasstrace]\")) {\n return { action: \"skipped\" };\n }\n\n const lines = content.split(\"\\n\");\n const startIdx = lines.findIndex(\n (l) => l.trim() === \"[mcp_servers.glasstrace]\",\n );\n if (startIdx === -1) {\n return { action: \"skipped\" };\n }\n\n // Find the end of the glasstrace section: next section header or end of file\n let endIdx = lines.length;\n for (let i = startIdx + 1; i < lines.length; i++) {\n if (/^\\s*\\[/.test(lines[i])) {\n endIdx = i;\n break;\n }\n }\n\n // Remove the section and any trailing blank lines\n const before = lines.slice(0, startIdx);\n const after = lines.slice(endIdx);\n\n // Trim trailing blank lines from the before section\n while (before.length > 0 && before[before.length - 1].trim() === \"\") {\n before.pop();\n }\n\n const result = [...before, ...after].join(\"\\n\").trimEnd();\n\n // Check if there are any remaining sections\n if (result.trim().length === 0) {\n return { action: \"deleted\" };\n }\n\n return { action: \"removed-section\", content: result + \"\\n\" };\n}\n\n/**\n * Writes the `.glasstrace/shutdown-requested` marker file atomically so\n * that a running SDK heartbeat tick (or equivalent lifecycle hook) can\n * detect that uninit has been invoked and trigger shutdown (DISC-1247\n * Scenario 1).\n *\n * Uses write-temp + rename semantics so a mid-write crash cannot leave\n * a truncated marker that the running process might misread.\n *\n * Best-effort: if `.glasstrace/` does not exist or the write fails, the\n * marker is silently skipped — uninit's cleanup is not blocked by a\n * missing running process.\n *\n * @internal Exported for unit testing only.\n */\nexport function writeShutdownMarker(projectRoot: string): boolean {\n const dirPath = path.join(projectRoot, \".glasstrace\");\n if (!fs.existsSync(dirPath)) {\n // No .glasstrace/ directory means no running SDK state is tracked —\n // nothing to signal. The filesystem removal step will handle any\n // stray artifacts.\n return false;\n }\n const markerPath = path.join(dirPath, \"shutdown-requested\");\n const tmpPath = `${markerPath}.tmp`;\n const body = JSON.stringify({ requestedAt: new Date().toISOString() });\n try {\n fs.writeFileSync(tmpPath, body, { encoding: \"utf-8\", mode: 0o600 });\n try {\n fs.chmodSync(tmpPath, 0o600);\n } catch {\n // chmod may be unsupported on some filesystems; proceed with rename.\n }\n fs.renameSync(tmpPath, markerPath);\n return true;\n } catch {\n // Best-effort cleanup of the temp file; swallow errors so uninit\n // itself never fails because of a signal-side-channel write.\n try {\n fs.unlinkSync(tmpPath);\n } catch {\n // Ignore — the marker was best-effort to begin with.\n }\n return false;\n }\n}\n\n/**\n * Simple TTY prompt used when `UninitOptions.prompt` is not provided.\n * Returns `defaultValue` when stdin is not a TTY.\n */\nasync function defaultPrompt(question: string, defaultValue: boolean): Promise<boolean> {\n if (!process.stdin.isTTY) return defaultValue;\n const readline = await import(\"node:readline\");\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n return new Promise<boolean>((resolve) => {\n const suffix = defaultValue ? \" [Y/n] \" : \" [y/N] \";\n rl.question(question + suffix, (answer) => {\n rl.close();\n const trimmed = answer.trim().toLowerCase();\n if (trimmed === \"\") {\n resolve(defaultValue);\n return;\n }\n resolve(trimmed === \"y\" || trimmed === \"yes\");\n });\n });\n}\n\n/**\n * Reverses every step of `glasstrace init`, cleanly removing all SDK artifacts\n * from a project.\n *\n * Steps (in order):\n * 1. Write `.glasstrace/shutdown-requested` marker so a running SDK can\n * drain and exit cleanly (DISC-1247 Scenario 1)\n * 2. Unwrap `withGlasstraceConfig` from next.config\n * 3. Remove `registerGlasstrace` from instrumentation.ts (or delete if init-created)\n * 4. Remove `.glasstrace/` directory\n * 4a. Remove `<staticRoot>/.well-known/glasstrace.json` (and prune the\n * enclosing `.well-known/` directory when empty)\n * 5. Remove `GLASSTRACE_*` entries from `.env.local` (with dev-key confirmation)\n * 6. Remove `.glasstrace/` from `.gitignore`\n * 7. Remove MCP config entries\n * 8. Remove info sections from agent files\n *\n * @param options - Configuration for the uninit command.\n * @returns A structured result describing what actions were taken.\n */\nexport async function runUninit(options: UninitOptions): Promise<UninitResult> {\n const { projectRoot, dryRun } = options;\n const force = options.force === true;\n const prompt = options.prompt ?? defaultPrompt;\n const summary: string[] = [];\n const warnings: string[] = [];\n const errors: string[] = [];\n const prefix = dryRun ? \"[dry run] \" : \"\";\n\n // Step 0: Signal any running SDK to shut down via a marker file.\n // Placed first so the running process has maximum time to observe\n // the marker while the remaining cleanup steps execute.\n try {\n if (!dryRun) {\n const markerWritten = writeShutdownMarker(projectRoot);\n if (markerWritten) {\n summary.push(\"Wrote .glasstrace/shutdown-requested marker\");\n }\n } else {\n const dirPath = path.join(projectRoot, \".glasstrace\");\n if (fs.existsSync(dirPath)) {\n summary.push(`${prefix}Would write .glasstrace/shutdown-requested marker`);\n }\n }\n } catch (err) {\n // Marker is best-effort; failure is not an error for uninit.\n warnings.push(\n `Shutdown marker write failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Step 1: Unwrap withGlasstraceConfig from next.config\n try {\n let configHandled = false;\n for (const name of NEXT_CONFIG_NAMES) {\n const configPath = path.join(projectRoot, name);\n if (!fs.existsSync(configPath)) {\n continue;\n }\n\n const content = fs.readFileSync(configPath, \"utf-8\");\n if (!content.includes(\"withGlasstraceConfig\")) {\n continue;\n }\n\n const isESM = name.endsWith(\".ts\") || name.endsWith(\".mjs\");\n const unwrapResult = isESM\n ? unwrapExport(content)\n : unwrapCJSExport(content);\n\n if (unwrapResult.unwrapped) {\n const cleaned = removeGlasstraceConfigImport(unwrapResult.content);\n const final = cleanLeadingBlankLines(cleaned);\n if (!dryRun) {\n fs.writeFileSync(configPath, final, \"utf-8\");\n }\n summary.push(`${prefix}Unwrapped withGlasstraceConfig from ${name}`);\n configHandled = true;\n break;\n } else {\n warnings.push(\n `${name} contains withGlasstraceConfig but could not be automatically unwrapped. ` +\n \"Please remove withGlasstraceConfig() manually.\",\n );\n configHandled = true;\n break;\n }\n }\n if (!configHandled) {\n // No next.config with withGlasstraceConfig found — nothing to do\n }\n } catch (err) {\n errors.push(\n `Failed to process next.config: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Step 2: Remove registerGlasstrace from instrumentation.ts\n try {\n const instrPath = path.join(projectRoot, \"instrumentation.ts\");\n if (fs.existsSync(instrPath)) {\n const content = fs.readFileSync(instrPath, \"utf-8\");\n if (content.includes(\"registerGlasstrace\") || content.includes(\"@glasstrace/sdk\")) {\n if (isInitCreatedInstrumentation(content)) {\n if (!dryRun) {\n fs.unlinkSync(instrPath);\n }\n summary.push(`${prefix}Deleted instrumentation.ts (init-created)`);\n } else {\n const cleaned = removeRegisterGlasstrace(content);\n if (cleaned !== content) {\n if (!dryRun) {\n fs.writeFileSync(instrPath, cleaned, \"utf-8\");\n }\n summary.push(\n `${prefix}Removed registerGlasstrace() from instrumentation.ts`,\n );\n }\n }\n }\n }\n } catch (err) {\n errors.push(\n `Failed to process instrumentation.ts: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Step 3: Remove .glasstrace/ directory\n try {\n const glasstraceDir = path.join(projectRoot, \".glasstrace\");\n if (fs.existsSync(glasstraceDir)) {\n if (!dryRun) {\n fs.rmSync(glasstraceDir, { recursive: true, force: true });\n }\n summary.push(`${prefix}Removed .glasstrace/ directory`);\n }\n } catch (err) {\n errors.push(\n `Failed to remove .glasstrace/: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Step 3a: Remove the static discovery file at\n // `<staticRoot>/.well-known/glasstrace.json` and, when empty, the\n // enclosing `.well-known/` directory. Sibling files (e.g. a user's\n // own `security.txt`) are never touched.\n try {\n if (dryRun) {\n // Dry-run preview: simulate the removal by checking existence only.\n // `removeDiscoveryFile` is a destructive helper, so the preview path\n // replicates the existence check inline rather than invoking it.\n // This keeps dry-run accurate even if the helper is changed later.\n // Mirrors the real sweep by checking BOTH candidate layouts so an\n // orphaned file in the non-inferred directory still shows up in\n // the preview (heuristic-drift scenario from the Codex re-review).\n for (const previewLayout of [\"public\", \"static\"] as const) {\n const relPath = relativeDiscoveryPath(previewLayout);\n const absPath = path.join(projectRoot, relPath);\n if (fs.existsSync(absPath)) {\n summary.push(`${prefix}Would remove ${relPath}`);\n }\n }\n } else {\n const result = removeDiscoveryFile(projectRoot);\n if (result.action === \"removed\") {\n const relPath = relativeDiscoveryPath(result.layout);\n summary.push(`Removed ${relPath}`);\n if (result.directoryRemoved) {\n const dirRel = relPath.replace(/\\/glasstrace\\.json$/, \"/\");\n summary.push(`Removed empty ${dirRel}`);\n }\n } else if (result.action === \"failed\") {\n warnings.push(\n `Failed to remove ${relativeDiscoveryPath(result.layout)}${\n result.error !== undefined ? `: ${result.error}` : \"\"\n }`,\n );\n }\n }\n } catch (err) {\n warnings.push(\n `Failed to remove discovery file: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Step 4: Remove GLASSTRACE entries from .env.local\n // DISC-1247 Scenario 6: if the file contains a claimed developer key\n // (`gt_dev_*`), require explicit confirmation before removing it so\n // users don't silently lose authentication state during uninit.\n // `--force` bypasses the prompt.\n try {\n const envPath = path.join(projectRoot, \".env.local\");\n if (fs.existsSync(envPath)) {\n const content = fs.readFileSync(envPath, \"utf-8\");\n const existingKey = readEnvLocalApiKey(content);\n const hasDevKey = isDevApiKey(existingKey);\n\n // Track how the dev-key path is resolved so the summary reflects\n // what actually happened: prompt-confirmed, force-bypassed, or\n // preview-only. Using the literal \"(dev key confirmed)\" for all\n // three paths was misleading (Copilot review).\n let proceed = true;\n let devKeyPath: \"interactive-confirmed\" | \"force-bypass\" | \"dry-run-preview\" | \"none\" = \"none\";\n if (hasDevKey) {\n if (dryRun) {\n devKeyPath = \"dry-run-preview\";\n } else if (force) {\n devKeyPath = \"force-bypass\";\n } else {\n const confirmed = await prompt(\n \".env.local contains a claimed Glasstrace developer API key (gt_dev_...). \" +\n \"Removing it will require you to re-authenticate. Continue?\",\n false,\n );\n proceed = confirmed;\n if (confirmed) devKeyPath = \"interactive-confirmed\";\n }\n }\n\n if (!proceed) {\n warnings.push(\n \"Preserved GLASSTRACE_API_KEY in .env.local (claimed dev key; re-run with --force to remove)\",\n );\n } else {\n const lines = content.split(\"\\n\");\n const filtered = lines.filter((line) => {\n const trimmed = line.trim();\n // Match both commented and uncommented GLASSTRACE_ lines\n return !(\n /^\\s*#?\\s*GLASSTRACE_API_KEY\\s*=/.test(trimmed) ||\n /^\\s*#?\\s*GLASSTRACE_COVERAGE_MAP\\s*=/.test(trimmed)\n );\n });\n\n if (filtered.length !== lines.length) {\n const result = filtered.join(\"\\n\");\n // If the file is now empty (only newlines), don't write it\n if (result.trim().length === 0) {\n if (!dryRun) {\n fs.unlinkSync(envPath);\n }\n summary.push(`${prefix}Deleted .env.local (no remaining entries)`);\n } else {\n if (!dryRun) {\n fs.writeFileSync(envPath, result, \"utf-8\");\n }\n let devKeyAnnotation = \"\";\n if (devKeyPath === \"interactive-confirmed\") {\n devKeyAnnotation = \" (dev key confirmed)\";\n } else if (devKeyPath === \"force-bypass\") {\n devKeyAnnotation = \" (dev key removed via --force)\";\n } else if (devKeyPath === \"dry-run-preview\") {\n devKeyAnnotation =\n \" (dev key would be removed; real run would require confirmation)\";\n }\n summary.push(\n `${prefix}Removed GLASSTRACE entries from .env.local${devKeyAnnotation}`,\n );\n }\n }\n }\n }\n } catch (err) {\n errors.push(\n `Failed to process .env.local: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Step 5: Remove .glasstrace/ from .gitignore\n try {\n const gitignorePath = path.join(projectRoot, \".gitignore\");\n if (fs.existsSync(gitignorePath)) {\n const content = fs.readFileSync(gitignorePath, \"utf-8\");\n const lines = content.split(\"\\n\");\n\n // Remove lines that are exactly \".glasstrace/\" or MCP config file entries\n // added by init (e.g., \".mcp.json\", \".cursor/mcp.json\", \".gemini/settings.json\",\n // \".codex/config.toml\")\n const mcpGitignoreEntries = new Set([\n \".glasstrace/\",\n \".mcp.json\",\n \".cursor/mcp.json\",\n \".gemini/settings.json\",\n \".codex/config.toml\",\n ]);\n\n const filtered = lines.filter(\n (line) => !mcpGitignoreEntries.has(line.trim()),\n );\n\n if (filtered.length !== lines.length) {\n const result = filtered.join(\"\\n\");\n if (result.trim().length === 0) {\n if (!dryRun) {\n fs.unlinkSync(gitignorePath);\n }\n summary.push(`${prefix}Deleted .gitignore (no remaining entries)`);\n } else {\n if (!dryRun) {\n fs.writeFileSync(gitignorePath, result, \"utf-8\");\n }\n summary.push(`${prefix}Removed Glasstrace entries from .gitignore`);\n }\n }\n }\n } catch (err) {\n errors.push(\n `Failed to process .gitignore: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Step 6: Remove MCP config entries\n try {\n for (const configFile of MCP_CONFIG_FILES) {\n const configPath = path.join(projectRoot, configFile);\n if (!fs.existsSync(configPath)) {\n continue;\n }\n\n const content = fs.readFileSync(configPath, \"utf-8\");\n const result = processJsonMcpConfig(content);\n\n if (result.action === \"deleted\") {\n if (!dryRun) {\n fs.unlinkSync(configPath);\n }\n summary.push(`${prefix}Deleted ${configFile}`);\n } else if (result.action === \"removed-key\" && result.content !== undefined) {\n if (!dryRun) {\n fs.writeFileSync(configPath, result.content, \"utf-8\");\n }\n summary.push(`${prefix}Removed glasstrace from ${configFile}`);\n }\n }\n // Handle Codex TOML config separately\n const codexConfigPath = path.join(projectRoot, \".codex\", \"config.toml\");\n if (fs.existsSync(codexConfigPath)) {\n const content = fs.readFileSync(codexConfigPath, \"utf-8\");\n const tomlResult = processTomlMcpConfig(content);\n\n if (tomlResult.action === \"deleted\") {\n if (!dryRun) {\n fs.unlinkSync(codexConfigPath);\n }\n summary.push(`${prefix}Deleted .codex/config.toml`);\n } else if (tomlResult.action === \"removed-section\" && tomlResult.content !== undefined) {\n if (!dryRun) {\n fs.writeFileSync(codexConfigPath, tomlResult.content, \"utf-8\");\n }\n summary.push(`${prefix}Removed glasstrace from .codex/config.toml`);\n }\n }\n\n // Handle Windsurf global config at ~/.codeium/windsurf/mcp_config.json\n // Only process if the project has Windsurf markers, to avoid touching\n // global config for non-Windsurf projects\n const hasWindsurfMarkers =\n fs.existsSync(path.join(projectRoot, \".windsurfrules\")) ||\n fs.existsSync(path.join(projectRoot, \".windsurf\"));\n if (hasWindsurfMarkers) {\n const windsurfConfigPath = path.join(\n os.homedir(),\n \".codeium\",\n \"windsurf\",\n \"mcp_config.json\",\n );\n if (fs.existsSync(windsurfConfigPath)) {\n const content = fs.readFileSync(windsurfConfigPath, \"utf-8\");\n const windsurfResult = processJsonMcpConfig(content);\n\n // Display the path with ~ for the home directory to keep output\n // readable, but derive it from the actual path for accuracy.\n const home = os.homedir();\n const displayPath = windsurfConfigPath.startsWith(home)\n ? \"~\" + windsurfConfigPath.slice(home.length)\n : windsurfConfigPath;\n\n if (windsurfResult.action === \"deleted\") {\n if (!dryRun) {\n fs.unlinkSync(windsurfConfigPath);\n }\n summary.push(\n `${prefix}Deleted global Windsurf config (${displayPath})`,\n );\n } else if (\n windsurfResult.action === \"removed-key\" &&\n windsurfResult.content !== undefined\n ) {\n if (!dryRun) {\n fs.writeFileSync(windsurfConfigPath, windsurfResult.content, \"utf-8\");\n }\n summary.push(\n `${prefix}Removed glasstrace from global Windsurf config (${displayPath})`,\n );\n }\n }\n }\n } catch (err) {\n errors.push(\n `Failed to process MCP config: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // Step 7: Remove info sections from agent files\n try {\n for (const infoFile of AGENT_INFO_FILES) {\n const filePath = path.join(projectRoot, infoFile);\n if (!fs.existsSync(filePath)) {\n continue;\n }\n\n const content = fs.readFileSync(filePath, \"utf-8\");\n const result = removeMarkerSection(content);\n\n if (result.removed) {\n if (result.content.trim().length === 0) {\n // File is now empty after removing the marker section —\n // only delete if the file was solely glasstrace content\n if (!dryRun) {\n fs.unlinkSync(filePath);\n }\n summary.push(`${prefix}Deleted ${infoFile} (only contained Glasstrace section)`);\n } else {\n if (!dryRun) {\n fs.writeFileSync(filePath, result.content, \"utf-8\");\n }\n summary.push(`${prefix}Removed Glasstrace section from ${infoFile}`);\n }\n }\n }\n } catch (err) {\n errors.push(\n `Failed to process agent info files: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n if (summary.length === 0 && errors.length === 0) {\n summary.push(\"No Glasstrace artifacts found — nothing to do.\");\n }\n\n return { exitCode: errors.length > 0 ? 1 : 0, summary, warnings, errors };\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { AnonApiKeySchema } from \"@glasstrace/protocol\";\nimport type { AnonApiKey } from \"@glasstrace/protocol\";\n\n/**\n * Standardized static discovery-file path, served at\n * `<static-root>/.well-known/glasstrace.json` (per RFC 8615) with\n * MIME type `application/json`.\n *\n * The SDK writes the file to this relative path under the\n * framework-specific static root (`public/` for Next.js, Remix, Astro;\n * `static/` for SvelteKit) and the browser extension fetches it from\n * the same path under the deployed origin.\n *\n * @drift-check RFC 8615 (https://www.rfc-editor.org/rfc/rfc8615) + ../glasstrace-product/docs/component-designs/sdk-2.0.md §7.1 Static discovery file\n */\nexport const WELL_KNOWN_GLASSTRACE_PATH = \".well-known/glasstrace.json\" as const;\n\n/**\n * Current schema version for `.well-known/glasstrace.json`. Consumers\n * (primarily the Glasstrace browser extension) MUST tolerate unknown\n * integers >= 1 per the forward-compatibility rule in the design doc\n * (\"SDK Discovery Endpoint / Static File — Component Design\", §5.3).\n */\nexport const DISCOVERY_FILE_VERSION = 1 as const;\n\n/**\n * Schema of the static discovery file written by `sdk init`.\n *\n * Version 1 defines exactly two required fields: `version` and `key`.\n * Additional fields may appear in later schema versions — consumers MUST\n * ignore unknown fields (forward-compatibility) and MUST reject files\n * whose `key` does not match `^gt_anon_[a-f0-9]{48}$`.\n */\nexport interface DiscoveryFileV1 {\n version: typeof DISCOVERY_FILE_VERSION;\n key: AnonApiKey;\n}\n\n/**\n * Detected framework-specific static root. `public` covers Next.js,\n * Remix, and Astro; `static` covers SvelteKit. No other frameworks\n * differ today per the design doc's §4.3 table.\n */\nexport type StaticRootLayout = \"public\" | \"static\";\n\n/**\n * Result returned by {@link resolveStaticRoot} so callers can report the\n * framework-specific path they targeted (used in init summary lines and\n * rollback output).\n */\nexport interface StaticRootResolution {\n /** Absolute path to the static root directory (may not exist yet). */\n absolutePath: string;\n /** Which layout was chosen. */\n layout: StaticRootLayout;\n}\n\n/**\n * Describes the outcome of a single call to {@link writeDiscoveryFile} so\n * callers can surface an accurate summary line without re-reading the\n * file. Mirrors the DISC-1247 Scenario 2 re-init preservation contract:\n * a valid file whose `key` already matches the on-disk anon key is left\n * alone rather than rewritten.\n */\nexport type WriteDiscoveryAction =\n | \"created\"\n | \"updated-stale\"\n | \"skipped-matches\"\n | \"skipped-foreign\"\n | \"failed\";\n\n/**\n * Structured result from {@link writeDiscoveryFile}.\n */\nexport interface WriteDiscoveryResult {\n action: WriteDiscoveryAction;\n /** Absolute path of the discovery file (whether or not it was written). */\n filePath: string;\n /** Static root that was resolved, useful for `.gitignore` wiring. */\n layout: StaticRootLayout;\n /**\n * When `action === \"failed\"`, a short human-readable reason. Never\n * contains anon key bytes — callers can forward it to logs safely.\n */\n error?: string;\n}\n\n/**\n * Detects the project's framework-specific static root using the ordered\n * check from §4.4 of the design doc:\n *\n * 1. Classify as SvelteKit (→ `static/`) when `package.json` declares\n * `\"type\": \"module\"` AND the project contains `svelte.config.js` (or\n * `svelte.config.ts`) OR `src/app.html`. These signals together are\n * specific enough to avoid false positives on generic ESM projects.\n * 2. Otherwise use `public/` — this covers Next.js, Remix, Astro, and\n * plain Node web apps, which all serve `public/` verbatim.\n *\n * Returns the absolute directory path and the chosen layout. Does NOT\n * create the directory; callers use {@link writeDiscoveryFile}, which\n * creates any missing parents atomically.\n *\n * @internal Exported for unit testing only.\n */\nexport function resolveStaticRoot(projectRoot: string): StaticRootResolution {\n if (isSvelteKitProject(projectRoot)) {\n return {\n absolutePath: path.join(projectRoot, \"static\"),\n layout: \"static\",\n };\n }\n return {\n absolutePath: path.join(projectRoot, \"public\"),\n layout: \"public\",\n };\n}\n\n/**\n * Heuristic for SvelteKit detection. The design doc deliberately scopes\n * the check narrowly so a plain ESM library is never misclassified —\n * `svelte.config.{js,ts}` or `src/app.html` is the SvelteKit fingerprint,\n * and both must coexist with an ESM package.json.\n */\nfunction isSvelteKitProject(projectRoot: string): boolean {\n const pkgPath = path.join(projectRoot, \"package.json\");\n let isEsm = false;\n try {\n const pkgContent = fs.readFileSync(pkgPath, \"utf-8\");\n const parsed = JSON.parse(pkgContent) as { type?: unknown };\n isEsm = parsed.type === \"module\";\n } catch {\n // Missing or malformed package.json — fall through to default layout.\n return false;\n }\n if (!isEsm) return false;\n\n const svelteConfigJs = path.join(projectRoot, \"svelte.config.js\");\n const svelteConfigTs = path.join(projectRoot, \"svelte.config.ts\");\n const appHtml = path.join(projectRoot, \"src\", \"app.html\");\n return (\n fs.existsSync(svelteConfigJs) ||\n fs.existsSync(svelteConfigTs) ||\n fs.existsSync(appHtml)\n );\n}\n\n/**\n * Returns the project-relative path of the discovery file for the given\n * layout, suitable for surfacing in summary lines and `.gitignore` entries.\n */\nexport function relativeDiscoveryPath(layout: StaticRootLayout): string {\n const rootDir = layout === \"static\" ? \"static\" : \"public\";\n return `${rootDir}/${WELL_KNOWN_GLASSTRACE_PATH}`;\n}\n\n/**\n * Parses an existing discovery file and returns its key if the schema is\n * valid, or `null` when the file is missing, unreadable, not JSON, or\n * does not match the version-1 shape. The check is deliberately strict —\n * a corrupt or third-party-authored file is treated as \"no file\" so\n * {@link writeDiscoveryFile} overwrites it with a fresh SDK-managed copy.\n *\n * Extra unknown fields are tolerated (§5.3 forward-compatibility).\n *\n * @internal Exported for unit testing only.\n */\nexport function readExistingDiscoveryFile(\n filePath: string,\n): { key: AnonApiKey; extras: Record<string, unknown> } | null {\n let raw: string;\n try {\n raw = fs.readFileSync(filePath, \"utf-8\");\n } catch {\n return null;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return null;\n }\n\n if (\n parsed === null ||\n typeof parsed !== \"object\" ||\n Array.isArray(parsed)\n ) {\n return null;\n }\n\n const obj = parsed as Record<string, unknown>;\n const versionRaw = obj.version;\n if (\n typeof versionRaw !== \"number\" ||\n !Number.isInteger(versionRaw) ||\n versionRaw < 1\n ) {\n return null;\n }\n\n const keyResult = AnonApiKeySchema.safeParse(obj.key);\n if (!keyResult.success) {\n return null;\n }\n\n // Preserve user-added fields (extras) so re-init round-trips any custom\n // keys the consumer added. `version` and `key` are SDK-managed and\n // excluded from the extras object.\n const extras: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (k === \"version\" || k === \"key\") continue;\n extras[k] = v;\n }\n\n return { key: keyResult.data, extras };\n}\n\n/**\n * Serializes the discovery payload deterministically (pretty-printed JSON\n * with a trailing newline). Deterministic output keeps git diffs clean\n * when the file is checked in and matches the atomic-write contract:\n * byte-identical output on re-init when `extras` is unchanged.\n */\nfunction serializeDiscoveryPayload(\n key: AnonApiKey,\n extras: Record<string, unknown>,\n): string {\n // Key ordering: version, key, then extras in their original insertion\n // order. Preserves DISC-1247 Scenario 2 alignment — a user who added\n // `\"note\": \"…\"` after `\"key\"` sees the same ordering on re-init.\n const payload: Record<string, unknown> = {\n version: DISCOVERY_FILE_VERSION,\n key,\n ...extras,\n };\n return JSON.stringify(payload, null, 2) + \"\\n\";\n}\n\n/**\n * Writes the discovery file at `<staticRoot>/.well-known/glasstrace.json`\n * atomically.\n *\n * Behavior (per design doc §6.1 and §6.5):\n *\n * - When the target file does not exist, creates it with `{ version: 1,\n * key: <anonKey> }` after creating the `.well-known/` directory if\n * missing.\n * - When the target exists AND parses as a valid version-1 payload AND\n * its `key` matches the supplied `anonKey`: preserves the file (and\n * any user-added extra fields) and returns `\"skipped-matches\"`.\n * - When the target exists AND parses valid BUT its `key` does not\n * match: rewrites the file with the fresh key, preserving extras.\n * Returns `\"updated-stale\"`.\n * - When the target exists BUT fails to parse (corrupt, foreign-authored,\n * wrong schema): rewrites with a fresh SDK-managed payload and returns\n * `\"skipped-foreign\"` to signal that user content was not preserved.\n * - On any unexpected I/O error: returns `\"failed\"` with an error string.\n *\n * Uses a sibling temp file + `renameSync` for atomicity so concurrent\n * readers (e.g., a browser extension polling during dev server startup)\n * never observe a half-written file.\n *\n * @param projectRoot - Absolute path to the project root directory.\n * @param anonKey - The anon key currently on disk (see `anon-key.ts`).\n */\nexport function writeDiscoveryFile(\n projectRoot: string,\n anonKey: AnonApiKey,\n): WriteDiscoveryResult {\n const { absolutePath: staticRoot, layout } = resolveStaticRoot(projectRoot);\n const wellKnownDir = path.join(staticRoot, \".well-known\");\n const filePath = path.join(wellKnownDir, \"glasstrace.json\");\n\n let existingAction: WriteDiscoveryAction;\n let extras: Record<string, unknown> = {};\n\n if (fs.existsSync(filePath)) {\n const existing = readExistingDiscoveryFile(filePath);\n if (existing === null) {\n // Unreadable / malformed / non-SDK content — overwrite with a\n // fresh payload so the extension can discover the current key.\n // Extras are NOT preserved because we cannot safely parse them.\n existingAction = \"skipped-foreign\";\n } else if (existing.key === anonKey) {\n // Valid and already matches — leave the file alone (§6.5 step 2).\n return {\n action: \"skipped-matches\",\n filePath,\n layout,\n };\n } else {\n // Valid but stale — replace the key, preserve extras (§6.5 step 3).\n extras = existing.extras;\n existingAction = \"updated-stale\";\n }\n } else {\n existingAction = \"created\";\n }\n\n const tmpPath = `${filePath}.tmp-${process.pid}`;\n // On Windows, `renameSync` fails with EPERM/EEXIST when the\n // destination already exists. Rather than `unlink` the destination\n // first (which would cause data loss if the subsequent rename fails),\n // move the destination to a sibling backup path, commit the rename,\n // and only then delete the backup. If the rename fails, restore the\n // backup so the original file is preserved.\n const needsWindowsReplace =\n process.platform === \"win32\" && fs.existsSync(filePath);\n const backupPath = needsWindowsReplace\n ? `${filePath}.bak-${process.pid}`\n : null;\n\n try {\n fs.mkdirSync(wellKnownDir, { recursive: true });\n const payload = serializeDiscoveryPayload(anonKey, extras);\n fs.writeFileSync(tmpPath, payload, { encoding: \"utf-8\" });\n\n if (backupPath !== null) {\n fs.renameSync(filePath, backupPath);\n try {\n fs.renameSync(tmpPath, filePath);\n } catch (renameErr) {\n try {\n fs.renameSync(backupPath, filePath);\n } catch {\n // Restoration failed; nothing more we can do. Surface the\n // original rename error below so the caller sees the cause.\n }\n throw renameErr;\n }\n try {\n fs.unlinkSync(backupPath);\n } catch {\n // Backup cleanup is best-effort; a stale `.bak-<pid>` is\n // preferable to a spurious failure after a successful write.\n }\n } else {\n fs.renameSync(tmpPath, filePath);\n }\n\n return { action: existingAction, filePath, layout };\n } catch (err) {\n // Best-effort: remove the temp file if it was created before the\n // failure so a stale `.tmp-<pid>` does not clutter `.well-known/`.\n try {\n if (fs.existsSync(tmpPath)) {\n fs.unlinkSync(tmpPath);\n }\n } catch {\n // Swallow: the write has already failed; do not mask the root cause.\n }\n return {\n action: \"failed\",\n filePath,\n layout,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n/**\n * Describes the outcome of {@link removeDiscoveryFile}. `\"removed\"` means\n * the file existed and was deleted; `\"not-found\"` means there was nothing\n * to remove (no error). `\"failed\"` preserves an error string.\n */\nexport type RemoveDiscoveryAction = \"removed\" | \"not-found\" | \"failed\";\n\n/** Structured result from {@link removeDiscoveryFile}. */\nexport interface RemoveDiscoveryResult {\n action: RemoveDiscoveryAction;\n filePath: string;\n layout: StaticRootLayout;\n /** True when the enclosing `.well-known/` directory was removed too. */\n directoryRemoved: boolean;\n error?: string;\n}\n\n/**\n * Removes the discovery file written by {@link writeDiscoveryFile} if\n * present, and removes the enclosing `.well-known/` directory when it\n * becomes empty. Tolerant of missing files, missing directories, and\n * user-owned sibling content inside `.well-known/` (never deletes a\n * sibling file).\n *\n * Checks BOTH `public/.well-known/glasstrace.json` and\n * `static/.well-known/glasstrace.json` rather than only the\n * currently-inferred layout: if layout detection changes between\n * init and uninit (for example, a SvelteKit project has its\n * `package.json` modified so the heuristic no longer matches),\n * the file written under the original layout would otherwise\n * be orphaned.\n *\n * Matches the uninit contract from design doc §6.4.\n *\n * @param projectRoot - Absolute path to the project root directory.\n */\nexport function removeDiscoveryFile(\n projectRoot: string,\n): RemoveDiscoveryResult {\n const { layout: inferredLayout } = resolveStaticRoot(projectRoot);\n\n // Sweep both candidate layouts so an orphaned file in the non-inferred\n // location is still cleaned up. The returned layout describes where\n // a file was actually removed (preferring the inferred layout when a\n // file existed in both, which is not a supported state but is\n // tolerated); when neither layout had a file, the returned layout\n // mirrors the inferred one so callers surface a stable relative path.\n const layouts: StaticRootLayout[] = [\"public\", \"static\"];\n\n interface LayoutOutcome {\n layout: StaticRootLayout;\n filePath: string;\n removed: boolean;\n directoryRemoved: boolean;\n }\n const outcomes: LayoutOutcome[] = [];\n\n for (const layout of layouts) {\n const staticRoot = path.join(projectRoot, layout);\n const wellKnownDir = path.join(staticRoot, \".well-known\");\n const filePath = path.join(wellKnownDir, \"glasstrace.json\");\n\n let removed = false;\n try {\n if (fs.existsSync(filePath)) {\n fs.unlinkSync(filePath);\n removed = true;\n }\n } catch (err) {\n return {\n action: \"failed\",\n filePath,\n layout,\n directoryRemoved: false,\n error: err instanceof Error ? err.message : String(err),\n };\n }\n\n // Only attempt to prune the enclosing `.well-known/` when we actually\n // removed the discovery file from this layout. Pruning unconditionally\n // would delete a user-owned empty directory (that Glasstrace never\n // populated) as a silent side effect of `sdk uninit`.\n let directoryRemoved = false;\n if (removed) {\n try {\n if (fs.existsSync(wellKnownDir)) {\n const entries = fs.readdirSync(wellKnownDir);\n if (entries.length === 0) {\n fs.rmdirSync(wellKnownDir);\n directoryRemoved = true;\n }\n }\n } catch {\n // Best-effort cleanup; never surface as an error to uninit.\n }\n }\n\n outcomes.push({ layout, filePath, removed, directoryRemoved });\n }\n\n // Pick the outcome to report: prefer one where a file was removed. When\n // both layouts had a file (not a supported state, but tolerated),\n // prefer the inferred layout. When neither had a file, report the\n // inferred layout so callers receive a stable relative path.\n const removals = outcomes.filter((o) => o.removed);\n const chosen: LayoutOutcome = (() => {\n if (removals.length === 0) {\n return (\n outcomes.find((o) => o.layout === inferredLayout) ?? outcomes[0]!\n );\n }\n if (removals.length === 1) return removals[0]!;\n return (\n removals.find((o) => o.layout === inferredLayout) ?? removals[0]!\n );\n })();\n\n // Propagate directoryRemoved across both sweeps so the uninit summary\n // reflects every pruned directory even when only one was the primary.\n const anyDirectoryRemoved = outcomes.some((o) => o.directoryRemoved);\n\n return {\n action: removals.length > 0 ? \"removed\" : \"not-found\",\n filePath: chosen.filePath,\n layout: chosen.layout,\n directoryRemoved: chosen.directoryRemoved || anyDirectoryRemoved,\n };\n}\n"],"mappings":"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","names":["fs","path"]}
@@ -14098,6 +14098,7 @@ function deriveSessionId(apiKey, origin, date5, windowIndex) {
14098
14098
  }
14099
14099
 
14100
14100
  export {
14101
+ DevApiKeySchema,
14101
14102
  AnonApiKeySchema,
14102
14103
  createAnonApiKey,
14103
14104
  createBuildHash,
@@ -14110,4 +14111,4 @@ export {
14110
14111
  DEFAULT_CAPTURE_CONFIG,
14111
14112
  deriveSessionId
14112
14113
  };
14113
- //# sourceMappingURL=chunk-TQ54WLCZ.js.map
14114
+ //# sourceMappingURL=chunk-X5MAXP5T.js.map
@@ -33,7 +33,7 @@ import {
33
33
  performInit,
34
34
  recordSpansDropped,
35
35
  recordSpansExported
36
- } from "./chunk-DO2YPMQ5.js";
36
+ } from "./chunk-C567H5EQ.js";
37
37
  import {
38
38
  isAnonymousMode,
39
39
  isProductionDisabled,
@@ -42,11 +42,11 @@ import {
42
42
  import {
43
43
  getOrCreateAnonKey,
44
44
  readAnonKey
45
- } from "./chunk-IP4NMDJK.js";
45
+ } from "./chunk-3LILTM3T.js";
46
46
  import {
47
47
  GLASSTRACE_ATTRIBUTE_NAMES,
48
48
  deriveSessionId
49
- } from "./chunk-TQ54WLCZ.js";
49
+ } from "./chunk-X5MAXP5T.js";
50
50
  import {
51
51
  __require
52
52
  } from "./chunk-NSBPE2FW.js";
@@ -137,6 +137,126 @@ function classifyFetchTarget(url) {
137
137
  return "unknown";
138
138
  }
139
139
 
140
+ // src/error-response-body.ts
141
+ var ERROR_RESPONSE_BODY_MAX_BYTES = 4096;
142
+ var ERROR_RESPONSE_BODY_TRUNCATION_MARKER = "...[truncated]";
143
+ var REDACTED = "[REDACTED]";
144
+ var ERROR_STATUS_MIN = 400;
145
+ var ERROR_STATUS_MAX = 599;
146
+ function isHttpErrorStatus(status) {
147
+ let numeric;
148
+ if (typeof status === "number") {
149
+ numeric = status;
150
+ } else if (typeof status === "string" && status.length > 0) {
151
+ numeric = Number(status);
152
+ } else {
153
+ return false;
154
+ }
155
+ if (!Number.isFinite(numeric)) return false;
156
+ return numeric >= ERROR_STATUS_MIN && numeric <= ERROR_STATUS_MAX;
157
+ }
158
+ var REDACTION_PATTERNS = [
159
+ // Order matters: redact specific token shapes BEFORE the generic
160
+ // key=value catcher so a literal `Bearer eyJ…` collapses into a single
161
+ // [REDACTED] and the JWT regex does not separately match the suffix.
162
+ {
163
+ name: "bearer",
164
+ // Case-insensitive on the scheme: HTTP frameworks and proxies
165
+ // round-trip the auth scheme with inconsistent casing
166
+ // (`Bearer`, `bearer`, `BEARER`), and a real token leaks just as
167
+ // badly under any of them.
168
+ pattern: /\bBearer\s+[A-Za-z0-9._\-+/=]+/gi
169
+ },
170
+ {
171
+ name: "jwt",
172
+ // Three base64url segments separated by dots. Real JWTs encode at
173
+ // minimum a small JSON header in the first segment, which alone is
174
+ // typically ≥10 chars after base64url; a 16-char floor avoids false
175
+ // positives on dotted text like a stack-trace frame
176
+ // (`react.dom.server`) while still catching every real JWT we have
177
+ // seen in the wild. Anchored with word boundaries on both sides so
178
+ // a 3-dot semantic version like "next@15.4.1.2" does not match.
179
+ pattern: /\b[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g
180
+ },
181
+ {
182
+ name: "glasstrace-api-key",
183
+ // gt_dev_* and gt_anon_* keys are >=24 chars of [A-Za-z0-9].
184
+ pattern: /\bgt_(?:dev|anon)_[A-Za-z0-9]{16,}\b/g
185
+ },
186
+ {
187
+ name: "aws-access-key",
188
+ // 20-char prefix-fixed identifier.
189
+ pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g
190
+ },
191
+ {
192
+ name: "key-value-secret-quoted",
193
+ // Quoted-string variant: (key) [:=] "<value>". The value runs to
194
+ // the next unescaped closing quote so a multi-word secret like
195
+ // `password="my secret phrase"` is fully consumed instead of
196
+ // splitting at the first space and leaving the tail visible.
197
+ // The leading `(?<![A-Za-z0-9_])` prevents matching inside
198
+ // identifiers like `passwordless`. The trailing `"?` after the
199
+ // keyword absorbs the closing quote in JSON-style `"apikey":
200
+ // "value"` so the colon is still seen as the separator.
201
+ pattern: /(?<![A-Za-z0-9_])(?:api[_-]?key|apikey|secret|password|token)"?\s*[:=]\s*"(?:[^"\\]|\\.)*"/gi
202
+ },
203
+ {
204
+ name: "key-value-secret-bare",
205
+ // Unquoted variant: (key) [:=] <bare-value>. The bare value
206
+ // capture stops at common JSON/text delimiters so we redact only
207
+ // the value, not surrounding structure. Listed AFTER the quoted
208
+ // variant so a quoted value's surrounding `"` are consumed by
209
+ // the first pattern and we never fall through here for a quoted
210
+ // secret.
211
+ pattern: /(?<![A-Za-z0-9_])(?:api[_-]?key|apikey|secret|password|token)"?\s*[:=]\s*[^\s,;}\]"]+/gi
212
+ }
213
+ ];
214
+ function sanitizeErrorResponseBody(body) {
215
+ let out = body;
216
+ for (const { pattern } of REDACTION_PATTERNS) {
217
+ out = out.replace(pattern, REDACTED);
218
+ }
219
+ return out;
220
+ }
221
+ function truncateErrorResponseBody(body) {
222
+ const encoder = new TextEncoder();
223
+ const encoded = encoder.encode(body);
224
+ if (encoded.byteLength <= ERROR_RESPONSE_BODY_MAX_BYTES) {
225
+ return body;
226
+ }
227
+ let cut = ERROR_RESPONSE_BODY_MAX_BYTES;
228
+ let scan = cut - 1;
229
+ while (scan >= 0 && (encoded[scan] & 192) === 128) {
230
+ scan -= 1;
231
+ }
232
+ if (scan >= 0) {
233
+ const leading = encoded[scan];
234
+ let expected = 1;
235
+ if ((leading & 128) === 0) {
236
+ expected = 1;
237
+ } else if ((leading & 224) === 192) {
238
+ expected = 2;
239
+ } else if ((leading & 240) === 224) {
240
+ expected = 3;
241
+ } else if ((leading & 248) === 240) {
242
+ expected = 4;
243
+ }
244
+ if (scan + expected > cut) {
245
+ cut = scan;
246
+ }
247
+ }
248
+ const decoder = new TextDecoder("utf-8", { fatal: false });
249
+ const sliced = encoded.subarray(0, cut);
250
+ const decoded = decoder.decode(sliced);
251
+ return decoded + ERROR_RESPONSE_BODY_TRUNCATION_MARKER;
252
+ }
253
+ function prepareErrorResponseBody(body) {
254
+ if (body.length === 0) return null;
255
+ if (body.trim().length === 0) return null;
256
+ const sanitized = sanitizeErrorResponseBody(body);
257
+ return truncateErrorResponseBody(sanitized);
258
+ }
259
+
140
260
  // src/enriching-exporter.ts
141
261
  var ATTR = GLASSTRACE_ATTRIBUTE_NAMES;
142
262
  var API_KEY_PENDING = "pending";
@@ -365,7 +485,14 @@ var GlasstraceExporter = class {
365
485
  if (this.getConfig().errorResponseBodies) {
366
486
  const responseBody = attrs["glasstrace.internal.response_body"];
367
487
  if (typeof responseBody === "string") {
368
- extra[ATTR.ERROR_RESPONSE_BODY] = responseBody.slice(0, 500);
488
+ const enrichedStatus = extra[ATTR.HTTP_STATUS_CODE];
489
+ const effectiveStatus = typeof enrichedStatus === "number" ? enrichedStatus : statusCode;
490
+ if (isHttpErrorStatus(effectiveStatus)) {
491
+ const prepared = prepareErrorResponseBody(responseBody);
492
+ if (prepared !== null) {
493
+ extra[ATTR.ERROR_RESPONSE_BODY] = prepared;
494
+ }
495
+ }
369
496
  }
370
497
  }
371
498
  const spanAny = span;
@@ -4027,7 +4154,7 @@ function registerGlasstrace(options) {
4027
4154
  setCoreState(CoreState.REGISTERING);
4028
4155
  startRuntimeStateWriter({
4029
4156
  projectRoot: process.cwd(),
4030
- sdkVersion: "1.1.0"
4157
+ sdkVersion: "1.1.2"
4031
4158
  });
4032
4159
  const config = resolveConfig(options);
4033
4160
  if (config.verbose) {
@@ -4193,8 +4320,8 @@ async function backgroundInit(config, anonKeyForInit, generation) {
4193
4320
  if (config.verbose) {
4194
4321
  console.info("[glasstrace] Background init firing.");
4195
4322
  }
4196
- const healthReport = collectHealthReport("1.1.0");
4197
- const initResult = await performInit(config, anonKeyForInit, "1.1.0", healthReport);
4323
+ const healthReport = collectHealthReport("1.1.2");
4324
+ const initResult = await performInit(config, anonKeyForInit, "1.1.2", healthReport);
4198
4325
  if (generation !== registrationGeneration) return;
4199
4326
  const currentState = getCoreState();
4200
4327
  if (currentState === CoreState.SHUTTING_DOWN || currentState === CoreState.SHUTDOWN) {
@@ -4217,7 +4344,7 @@ async function backgroundInit(config, anonKeyForInit, generation) {
4217
4344
  }
4218
4345
  maybeInstallConsoleCapture();
4219
4346
  if (didLastInitSucceed()) {
4220
- startHeartbeat(config, anonKeyForInit, "1.1.0", generation, (newApiKey, accountId) => {
4347
+ startHeartbeat(config, anonKeyForInit, "1.1.2", generation, (newApiKey, accountId) => {
4221
4348
  setAuthState(AuthState.CLAIMING);
4222
4349
  emitLifecycleEvent("auth:claim_started", { accountId });
4223
4350
  setResolvedApiKey(newApiKey);
@@ -4512,7 +4639,7 @@ async function handleSourceMapUpload(distDir) {
4512
4639
  );
4513
4640
  return;
4514
4641
  }
4515
- const { discoverSourceMapFiles, computeBuildHash, uploadSourceMaps } = await import("./source-map-uploader-BJIXRLJ6.js");
4642
+ const { discoverSourceMapFiles, computeBuildHash, uploadSourceMaps } = await import("./source-map-uploader-DPUUCLNW.js");
4516
4643
  const files = await discoverSourceMapFiles(distDir);
4517
4644
  if (files.length === 0) {
4518
4645
  console.info("[glasstrace] No source map files found. Skipping upload.");
@@ -4566,4 +4693,4 @@ export {
4566
4693
  withGlasstraceConfig,
4567
4694
  captureError
4568
4695
  };
4569
- //# sourceMappingURL=chunk-ZBTC5QIQ.js.map
4696
+ //# sourceMappingURL=chunk-Z35HKVSO.js.map