@glasstrace/sdk 1.1.1 → 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 (46) hide show
  1. package/README.md +78 -1
  2. package/dist/{chunk-DIM4JRXM.js → chunk-2M57EO6U.js} +2 -2
  3. package/dist/{chunk-P22UQ2OJ.js → chunk-3LILTM3T.js} +1 -1
  4. package/dist/chunk-3LILTM3T.js.map +1 -0
  5. package/dist/{chunk-MXDZHFJQ.js → chunk-C567H5EQ.js} +2 -2
  6. package/dist/{chunk-7SZQN6IU.js → chunk-NB7GJE4S.js} +2 -2
  7. package/dist/chunk-NB7GJE4S.js.map +1 -0
  8. package/dist/{chunk-ZRDQ6ZKI.js → chunk-UJ2JC7PZ.js} +92 -473
  9. package/dist/chunk-UJ2JC7PZ.js.map +1 -0
  10. package/dist/{chunk-Y26HJUPD.js → chunk-Z35HKVSO.js} +135 -8
  11. package/dist/chunk-Z35HKVSO.js.map +1 -0
  12. package/dist/cli/init.cjs +463 -440
  13. package/dist/cli/init.cjs.map +1 -1
  14. package/dist/cli/init.js +434 -63
  15. package/dist/cli/init.js.map +1 -1
  16. package/dist/cli/mcp-add.cjs +14 -2
  17. package/dist/cli/mcp-add.cjs.map +1 -1
  18. package/dist/cli/mcp-add.js +17 -5
  19. package/dist/cli/mcp-add.js.map +1 -1
  20. package/dist/cli/status.cjs.map +1 -1
  21. package/dist/cli/status.js +1 -3
  22. package/dist/cli/status.js.map +1 -1
  23. package/dist/cli/uninit.cjs +3 -3
  24. package/dist/cli/uninit.cjs.map +1 -1
  25. package/dist/cli/uninit.js +3 -3
  26. package/dist/cli/validate.cjs +14162 -2
  27. package/dist/cli/validate.cjs.map +1 -1
  28. package/dist/cli/validate.d.cts +7 -3
  29. package/dist/cli/validate.d.ts +7 -3
  30. package/dist/cli/validate.js +25 -2
  31. package/dist/cli/validate.js.map +1 -1
  32. package/dist/index.cjs +134 -5
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.js +3 -3
  35. package/dist/{monorepo-GSL6JD3G.js → monorepo-PFVNPQ6X.js} +3 -5
  36. package/dist/node-entry.cjs +134 -5
  37. package/dist/node-entry.cjs.map +1 -1
  38. package/dist/node-entry.js +3 -3
  39. package/package.json +1 -1
  40. package/dist/chunk-7SZQN6IU.js.map +0 -1
  41. package/dist/chunk-P22UQ2OJ.js.map +0 -1
  42. package/dist/chunk-Y26HJUPD.js.map +0 -1
  43. package/dist/chunk-ZRDQ6ZKI.js.map +0 -1
  44. /package/dist/{chunk-DIM4JRXM.js.map → chunk-2M57EO6U.js.map} +0 -0
  45. /package/dist/{chunk-MXDZHFJQ.js.map → chunk-C567H5EQ.js.map} +0 -0
  46. /package/dist/{monorepo-GSL6JD3G.js.map → monorepo-PFVNPQ6X.js.map} +0 -0
package/dist/cli/init.cjs CHANGED
@@ -31,6 +31,26 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  ));
32
32
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
33
 
34
+ // src/cli/constants.ts
35
+ function formatAgentName(name) {
36
+ const displayNames = {
37
+ claude: "Claude Code",
38
+ codex: "Codex",
39
+ gemini: "Gemini",
40
+ cursor: "Cursor",
41
+ windsurf: "Windsurf",
42
+ generic: "Generic helper"
43
+ };
44
+ return displayNames[name];
45
+ }
46
+ var NEXT_CONFIG_NAMES;
47
+ var init_constants = __esm({
48
+ "src/cli/constants.ts"() {
49
+ "use strict";
50
+ NEXT_CONFIG_NAMES = ["next.config.ts", "next.config.js", "next.config.mjs"];
51
+ }
52
+ });
53
+
34
54
  // ../../node_modules/zod/v4/core/core.js
35
55
  // @__NO_SIDE_EFFECTS__
36
56
  function $constructor(name, initializer3, params) {
@@ -14932,454 +14952,50 @@ var init_mcp_runtime = __esm({
14932
14952
  }
14933
14953
  });
14934
14954
 
14935
- // src/cli/constants.ts
14936
- function formatAgentName(name) {
14937
- const displayNames = {
14938
- claude: "Claude Code",
14939
- codex: "Codex",
14940
- gemini: "Gemini",
14941
- cursor: "Cursor",
14942
- windsurf: "Windsurf",
14943
- generic: "Generic"
14944
- };
14945
- return displayNames[name];
14946
- }
14947
- var NEXT_CONFIG_NAMES;
14948
- var init_constants = __esm({
14949
- "src/cli/constants.ts"() {
14950
- "use strict";
14951
- init_mcp_runtime();
14952
- NEXT_CONFIG_NAMES = ["next.config.ts", "next.config.js", "next.config.mjs"];
14953
- }
14954
- });
14955
-
14956
- // src/cli/scaffolder.ts
14957
- function hasRegisterGlasstraceCall(content) {
14958
- return content.split("\n").some((line) => {
14959
- const uncommented = line.replace(/\/\/.*$/, "");
14960
- return /\bregisterGlasstrace\s*\(/.test(uncommented);
14961
- });
14962
- }
14963
- function injectRegisterGlasstrace(content) {
14964
- if (hasRegisterGlasstraceCall(content)) {
14965
- return { injected: false, content };
14966
- }
14967
- const registerFnRegex = /export\s+(?:async\s+)?function\s+register\s*\([^)]*\)\s*\{/;
14968
- const match = registerFnRegex.exec(content);
14969
- if (!match) {
14970
- return { injected: false, content };
14971
- }
14972
- const afterBrace = content.slice(match.index + match[0].length);
14973
- const indentMatch = /\n([ \t]+)/.exec(afterBrace);
14974
- const indent = indentMatch ? indentMatch[1] : " ";
14975
- const importLine = 'import { registerGlasstrace } from "@glasstrace/sdk";\n';
14976
- const hasGlasstraceImport2 = content.includes("@glasstrace/sdk");
14977
- const insertPoint = match.index + match[0].length;
14978
- const callInjection = `
14979
- ${indent}// Glasstrace must be registered before other instrumentation
14980
- ${indent}registerGlasstrace();
14981
- `;
14982
- let modified;
14983
- if (hasGlasstraceImport2) {
14984
- const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']@glasstrace\/sdk["']/;
14985
- const importMatch = importRegex.exec(content);
14986
- if (importMatch) {
14987
- const specifiers = importMatch[1];
14988
- const alreadyImported = specifiers.split(",").some((s) => s.trim() === "registerGlasstrace");
14989
- if (alreadyImported) {
14990
- modified = content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);
14991
- } else {
14992
- const existingImports = specifiers.trimEnd();
14993
- const separator = existingImports.endsWith(",") ? " " : ", ";
14994
- const updatedImport = `import { ${existingImports.trim()}${separator}registerGlasstrace } from "@glasstrace/sdk"`;
14995
- modified = content.replace(importMatch[0], updatedImport);
14996
- const newMatch = registerFnRegex.exec(modified);
14997
- if (newMatch) {
14998
- const newInsertPoint = newMatch.index + newMatch[0].length;
14999
- modified = modified.slice(0, newInsertPoint) + callInjection + modified.slice(newInsertPoint);
15000
- }
15001
- }
15002
- } else {
15003
- modified = importLine + content;
15004
- const newMatch = registerFnRegex.exec(modified);
15005
- if (newMatch) {
15006
- const newInsertPoint = newMatch.index + newMatch[0].length;
15007
- modified = modified.slice(0, newInsertPoint) + callInjection + modified.slice(newInsertPoint);
15008
- }
15009
- }
15010
- } else {
15011
- modified = importLine + content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);
15012
- }
15013
- return { injected: true, content: modified };
15014
- }
15015
- function resolveInstrumentationTarget(projectRoot) {
15016
- const rootExisting = [];
15017
- const srcExisting = [];
15018
- for (const name of INSTRUMENTATION_FILENAMES) {
15019
- const rootPath = path.join(projectRoot, name);
15020
- if (isRegularFile(rootPath)) {
15021
- rootExisting.push(rootPath);
15022
- }
15023
- const srcPath = path.join(projectRoot, "src", name);
15024
- if (isRegularFile(srcPath)) {
15025
- srcExisting.push(srcPath);
15026
- }
15027
- }
15028
- const existing = [...rootExisting, ...srcExisting];
15029
- if (rootExisting.length > 0 && srcExisting.length > 0) {
15030
- return {
15031
- target: null,
15032
- layout: null,
15033
- existing,
15034
- rootExisting,
15035
- srcExisting,
15036
- conflict: true
15037
- };
15038
- }
15039
- if (srcExisting.length > 0) {
15040
- return {
15041
- target: srcExisting[0],
15042
- layout: "src",
15043
- existing,
15044
- rootExisting,
15045
- srcExisting,
15046
- conflict: false
15047
- };
15048
- }
15049
- if (rootExisting.length > 0) {
15050
- return {
15051
- target: rootExisting[0],
15052
- layout: "root",
15053
- existing,
15054
- rootExisting,
15055
- srcExisting,
15056
- conflict: false
15057
- };
15058
- }
15059
- const srcDir = path.join(projectRoot, "src");
15060
- const layout = isDirectory(srcDir) ? "src" : "root";
15061
- const target = layout === "src" ? path.join(projectRoot, "src", "instrumentation.ts") : path.join(projectRoot, "instrumentation.ts");
15062
- return {
15063
- target,
15064
- layout,
15065
- existing,
15066
- rootExisting,
15067
- srcExisting,
15068
- conflict: false
15069
- };
15070
- }
15071
- function isDirectory(p) {
14955
+ // src/agent-detection/detect.ts
14956
+ async function pathExists(path10, mode = import_node_fs.constants.R_OK) {
15072
14957
  try {
15073
- return fs.statSync(p).isDirectory();
14958
+ await (0, import_promises.access)(path10, mode);
14959
+ return true;
15074
14960
  } catch {
15075
14961
  return false;
15076
14962
  }
15077
14963
  }
15078
- function isRegularFile(p) {
15079
- try {
15080
- const stat2 = fs.lstatSync(p);
15081
- if (stat2.isSymbolicLink()) {
15082
- return fs.statSync(p).isFile();
14964
+ async function findGitRoot(startDir) {
14965
+ let current = (0, import_node_path.resolve)(startDir);
14966
+ while (true) {
14967
+ if (await pathExists((0, import_node_path.join)(current, ".git"), import_node_fs.constants.F_OK)) {
14968
+ return current;
15083
14969
  }
15084
- return stat2.isFile();
15085
- } catch {
15086
- return false;
15087
- }
15088
- }
15089
- function appendRegisterFunction(content) {
15090
- const importLine = 'import { registerGlasstrace } from "@glasstrace/sdk";\n';
15091
- const functionBlock = "\nexport async function register() {\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}\n";
15092
- let withImport = content;
15093
- const hasGlasstraceImport2 = content.includes("@glasstrace/sdk");
15094
- if (!hasGlasstraceImport2) {
15095
- withImport = importLine + content;
15096
- } else {
15097
- const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']@glasstrace\/sdk["']/;
15098
- const importMatch = importRegex.exec(content);
15099
- if (importMatch) {
15100
- const specifiers = importMatch[1];
15101
- const alreadyImported = specifiers.split(",").some((s) => s.trim() === "registerGlasstrace");
15102
- if (!alreadyImported) {
15103
- const existingImports = specifiers.trimEnd();
15104
- const separator = existingImports.endsWith(",") ? " " : ", ";
15105
- const updatedImport = `import { ${existingImports.trim()}${separator}registerGlasstrace } from "@glasstrace/sdk"`;
15106
- withImport = content.replace(importMatch[0], updatedImport);
15107
- }
15108
- } else {
15109
- withImport = importLine + content;
14970
+ const parent = (0, import_node_path.dirname)(current);
14971
+ if (parent === current) {
14972
+ break;
15110
14973
  }
14974
+ current = parent;
15111
14975
  }
15112
- const trailingNewline = withImport.endsWith("\n") ? "" : "\n";
15113
- return withImport + trailingNewline + functionBlock;
14976
+ return (0, import_node_path.resolve)(startDir);
15114
14977
  }
15115
- async function defaultInstrumentationPrompt(question, defaultValue) {
15116
- if (!process.stdin.isTTY) return defaultValue;
15117
- const readline2 = await import("node:readline");
15118
- const rl = readline2.createInterface({
15119
- input: process.stdin,
15120
- output: process.stdout
15121
- });
14978
+ function isCliAvailable(binary) {
15122
14979
  return new Promise((resolve2) => {
15123
- const suffix = defaultValue ? " [Y/n] " : " [y/N] ";
15124
- rl.question(question + suffix, (answer) => {
15125
- rl.close();
15126
- const trimmed = answer.trim().toLowerCase();
15127
- if (trimmed === "") {
15128
- resolve2(defaultValue);
15129
- return;
15130
- }
15131
- resolve2(trimmed === "y" || trimmed === "yes");
14980
+ const command = process.platform === "win32" ? "where" : "which";
14981
+ (0, import_node_child_process.execFile)(command, [binary], (error48) => {
14982
+ resolve2(error48 === null);
15132
14983
  });
15133
14984
  });
15134
14985
  }
15135
- async function scaffoldInstrumentation(projectRoot, options = {}) {
15136
- const target = resolveInstrumentationTarget(projectRoot);
15137
- if (target.conflict) {
15138
- return {
15139
- action: "conflict",
15140
- // Point the user at the `src/` variant — modern Next.js apps with a
15141
- // `src/` directory load from there, so that's the merge target. The
15142
- // competing path is reported separately for the error message.
15143
- filePath: target.srcExisting[0],
15144
- conflictingPath: target.rootExisting[0]
15145
- };
14986
+ async function detectAgents(projectRoot) {
14987
+ const resolvedRoot = (0, import_node_path.resolve)(projectRoot);
14988
+ let rootStat;
14989
+ try {
14990
+ rootStat = await (0, import_promises.stat)(resolvedRoot);
14991
+ } catch (err) {
14992
+ const code = err.code;
14993
+ throw new Error(
14994
+ `projectRoot does not exist: ${resolvedRoot}` + (code ? ` (${code})` : "")
14995
+ );
15146
14996
  }
15147
- const filePath = target.target;
15148
- const layout = target.layout;
15149
- if (filePath === null || layout === null) {
15150
- return { action: "unrecognized" };
15151
- }
15152
- const force = options.force === true;
15153
- const prompt = options.prompt ?? defaultInstrumentationPrompt;
15154
- if (!fs.existsSync(filePath)) {
15155
- const content = `import { registerGlasstrace } from "@glasstrace/sdk";
15156
-
15157
- export async function register() {
15158
- // Glasstrace must be registered before Prisma instrumentation
15159
- // to ensure all ORM spans are captured correctly.
15160
- // If you use @prisma/instrumentation, import it after this call.
15161
- registerGlasstrace();
15162
- }
15163
- `;
15164
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
15165
- fs.writeFileSync(filePath, content, "utf-8");
15166
- return { action: "created", filePath, layout };
15167
- }
15168
- const existing = fs.readFileSync(filePath, "utf-8");
15169
- if (hasRegisterGlasstraceCall(existing)) {
15170
- return { action: "already-registered", filePath, layout };
15171
- }
15172
- if (!force) {
15173
- const approved = await prompt(
15174
- `Merge registerGlasstrace() into ${path.relative(projectRoot, filePath)}?`,
15175
- false
15176
- );
15177
- if (!approved) {
15178
- return { action: "skipped", filePath, layout };
15179
- }
15180
- }
15181
- const injectResult = injectRegisterGlasstrace(existing);
15182
- if (injectResult.injected) {
15183
- fs.writeFileSync(filePath, injectResult.content, "utf-8");
15184
- return { action: "injected", filePath, layout };
15185
- }
15186
- const appended = appendRegisterFunction(existing);
15187
- fs.writeFileSync(filePath, appended, "utf-8");
15188
- return { action: "appended", filePath, layout };
15189
- }
15190
- async function scaffoldNextConfig(projectRoot) {
15191
- let configPath;
15192
- let configName;
15193
- for (const name of NEXT_CONFIG_NAMES) {
15194
- const candidate = path.join(projectRoot, name);
15195
- if (fs.existsSync(candidate)) {
15196
- configPath = candidate;
15197
- configName = name;
15198
- break;
15199
- }
15200
- }
15201
- if (configPath === void 0 || configName === void 0) {
15202
- return null;
15203
- }
15204
- const existing = fs.readFileSync(configPath, "utf-8");
15205
- if (existing.trim().length === 0) {
15206
- return { modified: false, reason: "empty-file" };
15207
- }
15208
- if (existing.includes("withGlasstraceConfig")) {
15209
- return { modified: false, reason: "already-wrapped" };
15210
- }
15211
- const isESM = configName.endsWith(".ts") || configName.endsWith(".mjs");
15212
- if (isESM) {
15213
- const importLine = 'import { withGlasstraceConfig } from "@glasstrace/sdk";\n';
15214
- const wrapResult2 = wrapExport(existing);
15215
- if (!wrapResult2.wrapped) {
15216
- return { modified: false, reason: "no-export" };
15217
- }
15218
- const modified2 = importLine + "\n" + wrapResult2.content;
15219
- fs.writeFileSync(configPath, modified2, "utf-8");
15220
- return { modified: true };
15221
- }
15222
- const requireLine = 'const { withGlasstraceConfig } = require("@glasstrace/sdk");\n';
15223
- const wrapResult = wrapCJSExport(existing);
15224
- if (!wrapResult.wrapped) {
15225
- return { modified: false, reason: "no-export" };
15226
- }
15227
- const modified = requireLine + "\n" + wrapResult.content;
15228
- fs.writeFileSync(configPath, modified, "utf-8");
15229
- return { modified: true };
15230
- }
15231
- function wrapExport(content) {
15232
- const marker = "export default";
15233
- const idx = content.lastIndexOf(marker);
15234
- if (idx === -1) {
15235
- return { content, wrapped: false };
15236
- }
15237
- const preamble = content.slice(0, idx);
15238
- const exprRaw = content.slice(idx + marker.length);
15239
- const expr = exprRaw.trim().replace(/;?\s*$/, "");
15240
- if (expr.length === 0) {
15241
- return { content, wrapped: false };
15242
- }
15243
- return {
15244
- content: preamble + `export default withGlasstraceConfig(${expr});
15245
- `,
15246
- wrapped: true
15247
- };
15248
- }
15249
- function wrapCJSExport(content) {
15250
- const cjsMarker = "module.exports";
15251
- const cjsIdx = content.lastIndexOf(cjsMarker);
15252
- if (cjsIdx === -1) {
15253
- return { content, wrapped: false };
15254
- }
15255
- const preamble = content.slice(0, cjsIdx);
15256
- const afterMarker = content.slice(cjsIdx + cjsMarker.length);
15257
- const eqMatch = /^\s*=\s*/.exec(afterMarker);
15258
- if (!eqMatch) {
15259
- return { content, wrapped: false };
15260
- }
15261
- const exprRaw = afterMarker.slice(eqMatch[0].length);
15262
- const expr = exprRaw.trim().replace(/;?\s*$/, "");
15263
- if (expr.length === 0) {
15264
- return { content, wrapped: false };
15265
- }
15266
- return {
15267
- content: preamble + `module.exports = withGlasstraceConfig(${expr});
15268
- `,
15269
- wrapped: true
15270
- };
15271
- }
15272
- async function scaffoldEnvLocal(projectRoot) {
15273
- const filePath = path.join(projectRoot, ".env.local");
15274
- if (fs.existsSync(filePath)) {
15275
- const existing = fs.readFileSync(filePath, "utf-8");
15276
- if (/^\s*#?\s*GLASSTRACE_API_KEY\s*=/m.test(existing)) {
15277
- return false;
15278
- }
15279
- const separator = existing.endsWith("\n") ? "" : "\n";
15280
- fs.writeFileSync(filePath, existing + separator + "# GLASSTRACE_API_KEY=your_key_here\n", "utf-8");
15281
- return true;
15282
- }
15283
- fs.writeFileSync(filePath, "# GLASSTRACE_API_KEY=your_key_here\n", "utf-8");
15284
- return true;
15285
- }
15286
- async function addCoverageMapEnv(projectRoot) {
15287
- const filePath = path.join(projectRoot, ".env.local");
15288
- if (!fs.existsSync(filePath)) {
15289
- fs.writeFileSync(filePath, "GLASSTRACE_COVERAGE_MAP=true\n", "utf-8");
15290
- return true;
15291
- }
15292
- const existing = fs.readFileSync(filePath, "utf-8");
15293
- const keyRegex = /^(\s*GLASSTRACE_COVERAGE_MAP\s*=\s*)(.*)$/m;
15294
- const keyMatch = keyRegex.exec(existing);
15295
- if (keyMatch) {
15296
- const currentValue = keyMatch[2].trim();
15297
- if (currentValue === "true") {
15298
- return false;
15299
- }
15300
- const updated = existing.replace(keyRegex, `${keyMatch[1]}true`);
15301
- fs.writeFileSync(filePath, updated, "utf-8");
15302
- return true;
15303
- }
15304
- const separator = existing.endsWith("\n") ? "" : "\n";
15305
- fs.writeFileSync(filePath, existing + separator + "GLASSTRACE_COVERAGE_MAP=true\n", "utf-8");
15306
- return true;
15307
- }
15308
- async function scaffoldGitignore(projectRoot) {
15309
- const filePath = path.join(projectRoot, ".gitignore");
15310
- if (fs.existsSync(filePath)) {
15311
- const existing = fs.readFileSync(filePath, "utf-8");
15312
- const lines = existing.split("\n").map((l) => l.trim());
15313
- if (lines.includes(".glasstrace/")) {
15314
- return false;
15315
- }
15316
- const separator = existing.endsWith("\n") ? "" : "\n";
15317
- fs.writeFileSync(filePath, existing + separator + ".glasstrace/\n", "utf-8");
15318
- return true;
15319
- }
15320
- fs.writeFileSync(filePath, ".glasstrace/\n", "utf-8");
15321
- return true;
15322
- }
15323
- var fs, path, INSTRUMENTATION_FILENAMES;
15324
- var init_scaffolder = __esm({
15325
- "src/cli/scaffolder.ts"() {
15326
- "use strict";
15327
- fs = __toESM(require("node:fs"), 1);
15328
- path = __toESM(require("node:path"), 1);
15329
- init_constants();
15330
- init_mcp_runtime();
15331
- INSTRUMENTATION_FILENAMES = [
15332
- "instrumentation.ts",
15333
- "instrumentation.js",
15334
- "instrumentation.mjs"
15335
- ];
15336
- }
15337
- });
15338
-
15339
- // src/agent-detection/detect.ts
15340
- async function pathExists(path10, mode = import_node_fs.constants.R_OK) {
15341
- try {
15342
- await (0, import_promises.access)(path10, mode);
15343
- return true;
15344
- } catch {
15345
- return false;
15346
- }
15347
- }
15348
- async function findGitRoot(startDir) {
15349
- let current = (0, import_node_path.resolve)(startDir);
15350
- while (true) {
15351
- if (await pathExists((0, import_node_path.join)(current, ".git"), import_node_fs.constants.F_OK)) {
15352
- return current;
15353
- }
15354
- const parent = (0, import_node_path.dirname)(current);
15355
- if (parent === current) {
15356
- break;
15357
- }
15358
- current = parent;
15359
- }
15360
- return (0, import_node_path.resolve)(startDir);
15361
- }
15362
- function isCliAvailable(binary) {
15363
- return new Promise((resolve2) => {
15364
- const command = process.platform === "win32" ? "where" : "which";
15365
- (0, import_node_child_process.execFile)(command, [binary], (error48) => {
15366
- resolve2(error48 === null);
15367
- });
15368
- });
15369
- }
15370
- async function detectAgents(projectRoot) {
15371
- const resolvedRoot = (0, import_node_path.resolve)(projectRoot);
15372
- let rootStat;
15373
- try {
15374
- rootStat = await (0, import_promises.stat)(resolvedRoot);
15375
- } catch (err) {
15376
- const code = err.code;
15377
- throw new Error(
15378
- `projectRoot does not exist: ${resolvedRoot}` + (code ? ` (${code})` : "")
15379
- );
15380
- }
15381
- if (!rootStat.isDirectory()) {
15382
- throw new Error(`projectRoot is not a directory: ${resolvedRoot}`);
14997
+ if (!rootStat.isDirectory()) {
14998
+ throw new Error(`projectRoot is not a directory: ${resolvedRoot}`);
15383
14999
  }
15384
15000
  const gitRoot = await findGitRoot(resolvedRoot);
15385
15001
  const searchDirs = [];
@@ -16995,7 +16611,7 @@ var init_uninit = __esm({
16995
16611
  os = __toESM(require("node:os"), 1);
16996
16612
  path5 = __toESM(require("node:path"), 1);
16997
16613
  init_constants();
16998
- init_scaffolder();
16614
+ init_mcp_runtime();
16999
16615
  init_discovery_file();
17000
16616
  MCP_CONFIG_FILES = [".mcp.json", ".cursor/mcp.json", ".gemini/settings.json"];
17001
16617
  AGENT_INFO_FILES = [
@@ -17120,7 +16736,8 @@ async function mcpAdd(options) {
17120
16736
  }
17121
16737
  const agents = await detectAgents(projectRoot);
17122
16738
  const detectedNonGeneric = agents.filter((a) => a.name !== "generic");
17123
- const targetAgents = detectedNonGeneric.length > 0 ? detectedNonGeneric : agents.filter((a) => a.name === "generic");
16739
+ const genericAgent = agents.find((a) => a.name === "generic");
16740
+ const targetAgents = genericAgent ? [...detectedNonGeneric, genericAgent] : detectedNonGeneric;
17124
16741
  if (dryRun) {
17125
16742
  messages.push("Dry run: would perform the following actions:", "");
17126
16743
  for (const agent of targetAgents) {
@@ -17229,6 +16846,17 @@ async function mcpAdd(options) {
17229
16846
  " No agents detected. Place agent marker files (e.g., CLAUDE.md, .cursor/) in your project."
17230
16847
  );
17231
16848
  }
16849
+ const detectedNonGenericResults = results.filter(
16850
+ (r) => detectedNonGeneric.some((a) => a.name === r.agent)
16851
+ );
16852
+ const allDetectedNonGenericFailed = detectedNonGeneric.length > 0 && !detectedNonGenericResults.some((r) => r.success);
16853
+ if (allDetectedNonGenericFailed) {
16854
+ messages.push(
16855
+ "",
16856
+ "All detected agent registrations failed. Check errors above."
16857
+ );
16858
+ return { exitCode: 1, results, messages };
16859
+ }
17232
16860
  if (!anySuccess && results.length > 0) {
17233
16861
  messages.push(
17234
16862
  "",
@@ -17274,7 +16902,7 @@ function hasRegisterGlasstraceImport(content) {
17274
16902
  if (!importMatch) return false;
17275
16903
  return importMatch[1].split(",").map((s) => s.trim()).includes("registerGlasstrace");
17276
16904
  }
17277
- function runValidate(options) {
16905
+ async function runValidate(options) {
17278
16906
  const { projectRoot } = options;
17279
16907
  const issues = [];
17280
16908
  const glasstraceDir = path7.join(projectRoot, ".glasstrace");
@@ -17319,6 +16947,22 @@ function runValidate(options) {
17319
16947
  fix: "Run `npx glasstrace init` to re-register the marker, or `npx glasstrace uninit` to fully remove MCP configuration."
17320
16948
  });
17321
16949
  }
16950
+ if (markerExists) {
16951
+ try {
16952
+ const [markerState, resolved] = await Promise.all([
16953
+ readMcpMarker(projectRoot),
16954
+ resolveEffectiveMcpCredential(projectRoot)
16955
+ ]);
16956
+ if (markerState.status === "valid" && resolved.effective !== null && markerState.credentialHash !== identityFingerprint(resolved.effective.key)) {
16957
+ issues.push({
16958
+ code: "mcp-helper-stale-credential",
16959
+ message: "Managed MCP configs were last refreshed with a different credential than the project is now using. MCP queries may return no traces while the dashboard sees them.",
16960
+ fix: "Run `npx glasstrace mcp add --force` to refresh managed MCP configs with the current credential."
16961
+ });
16962
+ }
16963
+ } catch {
16964
+ }
16965
+ }
17322
16966
  const summary = [];
17323
16967
  if (issues.length === 0) {
17324
16968
  summary.push("Glasstrace install state is consistent.");
@@ -17354,11 +16998,13 @@ var init_validate = __esm({
17354
16998
  "use strict";
17355
16999
  fs7 = __toESM(require("node:fs"), 1);
17356
17000
  path7 = __toESM(require("node:path"), 1);
17001
+ init_mcp_runtime();
17357
17002
  MCP_CONFIG_CANDIDATES = [
17358
17003
  ".mcp.json",
17359
17004
  ".cursor/mcp.json",
17360
17005
  ".gemini/settings.json",
17361
- ".codex/config.toml"
17006
+ ".codex/config.toml",
17007
+ ".glasstrace/mcp.json"
17362
17008
  ];
17363
17009
  }
17364
17010
  });
@@ -17571,7 +17217,384 @@ module.exports = __toCommonJS(init_exports);
17571
17217
  var fs9 = __toESM(require("node:fs"), 1);
17572
17218
  var path9 = __toESM(require("node:path"), 1);
17573
17219
  var readline = __toESM(require("node:readline"), 1);
17574
- init_scaffolder();
17220
+
17221
+ // src/cli/scaffolder.ts
17222
+ var fs = __toESM(require("node:fs"), 1);
17223
+ var path = __toESM(require("node:path"), 1);
17224
+ init_constants();
17225
+ function hasRegisterGlasstraceCall(content) {
17226
+ return content.split("\n").some((line) => {
17227
+ const uncommented = line.replace(/\/\/.*$/, "");
17228
+ return /\bregisterGlasstrace\s*\(/.test(uncommented);
17229
+ });
17230
+ }
17231
+ function injectRegisterGlasstrace(content) {
17232
+ if (hasRegisterGlasstraceCall(content)) {
17233
+ return { injected: false, content };
17234
+ }
17235
+ const registerFnRegex = /export\s+(?:async\s+)?function\s+register\s*\([^)]*\)\s*\{/;
17236
+ const match = registerFnRegex.exec(content);
17237
+ if (!match) {
17238
+ return { injected: false, content };
17239
+ }
17240
+ const afterBrace = content.slice(match.index + match[0].length);
17241
+ const indentMatch = /\n([ \t]+)/.exec(afterBrace);
17242
+ const indent = indentMatch ? indentMatch[1] : " ";
17243
+ const importLine = 'import { registerGlasstrace } from "@glasstrace/sdk";\n';
17244
+ const hasGlasstraceImport2 = content.includes("@glasstrace/sdk");
17245
+ const insertPoint = match.index + match[0].length;
17246
+ const callInjection = `
17247
+ ${indent}// Glasstrace must be registered before other instrumentation
17248
+ ${indent}registerGlasstrace();
17249
+ `;
17250
+ let modified;
17251
+ if (hasGlasstraceImport2) {
17252
+ const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']@glasstrace\/sdk["']/;
17253
+ const importMatch = importRegex.exec(content);
17254
+ if (importMatch) {
17255
+ const specifiers = importMatch[1];
17256
+ const alreadyImported = specifiers.split(",").some((s) => s.trim() === "registerGlasstrace");
17257
+ if (alreadyImported) {
17258
+ modified = content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);
17259
+ } else {
17260
+ const existingImports = specifiers.trimEnd();
17261
+ const separator = existingImports.endsWith(",") ? " " : ", ";
17262
+ const updatedImport = `import { ${existingImports.trim()}${separator}registerGlasstrace } from "@glasstrace/sdk"`;
17263
+ modified = content.replace(importMatch[0], updatedImport);
17264
+ const newMatch = registerFnRegex.exec(modified);
17265
+ if (newMatch) {
17266
+ const newInsertPoint = newMatch.index + newMatch[0].length;
17267
+ modified = modified.slice(0, newInsertPoint) + callInjection + modified.slice(newInsertPoint);
17268
+ }
17269
+ }
17270
+ } else {
17271
+ modified = importLine + content;
17272
+ const newMatch = registerFnRegex.exec(modified);
17273
+ if (newMatch) {
17274
+ const newInsertPoint = newMatch.index + newMatch[0].length;
17275
+ modified = modified.slice(0, newInsertPoint) + callInjection + modified.slice(newInsertPoint);
17276
+ }
17277
+ }
17278
+ } else {
17279
+ modified = importLine + content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);
17280
+ }
17281
+ return { injected: true, content: modified };
17282
+ }
17283
+ var INSTRUMENTATION_FILENAMES = [
17284
+ "instrumentation.ts",
17285
+ "instrumentation.js",
17286
+ "instrumentation.mjs"
17287
+ ];
17288
+ function resolveInstrumentationTarget(projectRoot) {
17289
+ const rootExisting = [];
17290
+ const srcExisting = [];
17291
+ for (const name of INSTRUMENTATION_FILENAMES) {
17292
+ const rootPath = path.join(projectRoot, name);
17293
+ if (isRegularFile(rootPath)) {
17294
+ rootExisting.push(rootPath);
17295
+ }
17296
+ const srcPath = path.join(projectRoot, "src", name);
17297
+ if (isRegularFile(srcPath)) {
17298
+ srcExisting.push(srcPath);
17299
+ }
17300
+ }
17301
+ const existing = [...rootExisting, ...srcExisting];
17302
+ if (rootExisting.length > 0 && srcExisting.length > 0) {
17303
+ return {
17304
+ target: null,
17305
+ layout: null,
17306
+ existing,
17307
+ rootExisting,
17308
+ srcExisting,
17309
+ conflict: true
17310
+ };
17311
+ }
17312
+ if (srcExisting.length > 0) {
17313
+ return {
17314
+ target: srcExisting[0],
17315
+ layout: "src",
17316
+ existing,
17317
+ rootExisting,
17318
+ srcExisting,
17319
+ conflict: false
17320
+ };
17321
+ }
17322
+ if (rootExisting.length > 0) {
17323
+ return {
17324
+ target: rootExisting[0],
17325
+ layout: "root",
17326
+ existing,
17327
+ rootExisting,
17328
+ srcExisting,
17329
+ conflict: false
17330
+ };
17331
+ }
17332
+ const srcDir = path.join(projectRoot, "src");
17333
+ const layout = isDirectory(srcDir) ? "src" : "root";
17334
+ const target = layout === "src" ? path.join(projectRoot, "src", "instrumentation.ts") : path.join(projectRoot, "instrumentation.ts");
17335
+ return {
17336
+ target,
17337
+ layout,
17338
+ existing,
17339
+ rootExisting,
17340
+ srcExisting,
17341
+ conflict: false
17342
+ };
17343
+ }
17344
+ function isDirectory(p) {
17345
+ try {
17346
+ return fs.statSync(p).isDirectory();
17347
+ } catch {
17348
+ return false;
17349
+ }
17350
+ }
17351
+ function isRegularFile(p) {
17352
+ try {
17353
+ const stat2 = fs.lstatSync(p);
17354
+ if (stat2.isSymbolicLink()) {
17355
+ return fs.statSync(p).isFile();
17356
+ }
17357
+ return stat2.isFile();
17358
+ } catch {
17359
+ return false;
17360
+ }
17361
+ }
17362
+ function appendRegisterFunction(content) {
17363
+ const importLine = 'import { registerGlasstrace } from "@glasstrace/sdk";\n';
17364
+ const functionBlock = "\nexport async function register() {\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}\n";
17365
+ let withImport = content;
17366
+ const hasGlasstraceImport2 = content.includes("@glasstrace/sdk");
17367
+ if (!hasGlasstraceImport2) {
17368
+ withImport = importLine + content;
17369
+ } else {
17370
+ const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']@glasstrace\/sdk["']/;
17371
+ const importMatch = importRegex.exec(content);
17372
+ if (importMatch) {
17373
+ const specifiers = importMatch[1];
17374
+ const alreadyImported = specifiers.split(",").some((s) => s.trim() === "registerGlasstrace");
17375
+ if (!alreadyImported) {
17376
+ const existingImports = specifiers.trimEnd();
17377
+ const separator = existingImports.endsWith(",") ? " " : ", ";
17378
+ const updatedImport = `import { ${existingImports.trim()}${separator}registerGlasstrace } from "@glasstrace/sdk"`;
17379
+ withImport = content.replace(importMatch[0], updatedImport);
17380
+ }
17381
+ } else {
17382
+ withImport = importLine + content;
17383
+ }
17384
+ }
17385
+ const trailingNewline = withImport.endsWith("\n") ? "" : "\n";
17386
+ return withImport + trailingNewline + functionBlock;
17387
+ }
17388
+ async function defaultInstrumentationPrompt(question, defaultValue) {
17389
+ if (!process.stdin.isTTY) return defaultValue;
17390
+ const readline2 = await import("node:readline");
17391
+ const rl = readline2.createInterface({
17392
+ input: process.stdin,
17393
+ output: process.stdout
17394
+ });
17395
+ return new Promise((resolve2) => {
17396
+ const suffix = defaultValue ? " [Y/n] " : " [y/N] ";
17397
+ rl.question(question + suffix, (answer) => {
17398
+ rl.close();
17399
+ const trimmed = answer.trim().toLowerCase();
17400
+ if (trimmed === "") {
17401
+ resolve2(defaultValue);
17402
+ return;
17403
+ }
17404
+ resolve2(trimmed === "y" || trimmed === "yes");
17405
+ });
17406
+ });
17407
+ }
17408
+ async function scaffoldInstrumentation(projectRoot, options = {}) {
17409
+ const target = resolveInstrumentationTarget(projectRoot);
17410
+ if (target.conflict) {
17411
+ return {
17412
+ action: "conflict",
17413
+ // Point the user at the `src/` variant — modern Next.js apps with a
17414
+ // `src/` directory load from there, so that's the merge target. The
17415
+ // competing path is reported separately for the error message.
17416
+ filePath: target.srcExisting[0],
17417
+ conflictingPath: target.rootExisting[0]
17418
+ };
17419
+ }
17420
+ const filePath = target.target;
17421
+ const layout = target.layout;
17422
+ if (filePath === null || layout === null) {
17423
+ return { action: "unrecognized" };
17424
+ }
17425
+ const force = options.force === true;
17426
+ const prompt = options.prompt ?? defaultInstrumentationPrompt;
17427
+ if (!fs.existsSync(filePath)) {
17428
+ const content = `import { registerGlasstrace } from "@glasstrace/sdk";
17429
+
17430
+ export async function register() {
17431
+ // Glasstrace must be registered before Prisma instrumentation
17432
+ // to ensure all ORM spans are captured correctly.
17433
+ // If you use @prisma/instrumentation, import it after this call.
17434
+ registerGlasstrace();
17435
+ }
17436
+ `;
17437
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
17438
+ fs.writeFileSync(filePath, content, "utf-8");
17439
+ return { action: "created", filePath, layout };
17440
+ }
17441
+ const existing = fs.readFileSync(filePath, "utf-8");
17442
+ if (hasRegisterGlasstraceCall(existing)) {
17443
+ return { action: "already-registered", filePath, layout };
17444
+ }
17445
+ if (!force) {
17446
+ const approved = await prompt(
17447
+ `Merge registerGlasstrace() into ${path.relative(projectRoot, filePath)}?`,
17448
+ false
17449
+ );
17450
+ if (!approved) {
17451
+ return { action: "skipped", filePath, layout };
17452
+ }
17453
+ }
17454
+ const injectResult = injectRegisterGlasstrace(existing);
17455
+ if (injectResult.injected) {
17456
+ fs.writeFileSync(filePath, injectResult.content, "utf-8");
17457
+ return { action: "injected", filePath, layout };
17458
+ }
17459
+ const appended = appendRegisterFunction(existing);
17460
+ fs.writeFileSync(filePath, appended, "utf-8");
17461
+ return { action: "appended", filePath, layout };
17462
+ }
17463
+ async function scaffoldNextConfig(projectRoot) {
17464
+ let configPath;
17465
+ let configName;
17466
+ for (const name of NEXT_CONFIG_NAMES) {
17467
+ const candidate = path.join(projectRoot, name);
17468
+ if (fs.existsSync(candidate)) {
17469
+ configPath = candidate;
17470
+ configName = name;
17471
+ break;
17472
+ }
17473
+ }
17474
+ if (configPath === void 0 || configName === void 0) {
17475
+ return null;
17476
+ }
17477
+ const existing = fs.readFileSync(configPath, "utf-8");
17478
+ if (existing.trim().length === 0) {
17479
+ return { modified: false, reason: "empty-file" };
17480
+ }
17481
+ if (existing.includes("withGlasstraceConfig")) {
17482
+ return { modified: false, reason: "already-wrapped" };
17483
+ }
17484
+ const isESM = configName.endsWith(".ts") || configName.endsWith(".mjs");
17485
+ if (isESM) {
17486
+ const importLine = 'import { withGlasstraceConfig } from "@glasstrace/sdk";\n';
17487
+ const wrapResult2 = wrapExport(existing);
17488
+ if (!wrapResult2.wrapped) {
17489
+ return { modified: false, reason: "no-export" };
17490
+ }
17491
+ const modified2 = importLine + "\n" + wrapResult2.content;
17492
+ fs.writeFileSync(configPath, modified2, "utf-8");
17493
+ return { modified: true };
17494
+ }
17495
+ const requireLine = 'const { withGlasstraceConfig } = require("@glasstrace/sdk");\n';
17496
+ const wrapResult = wrapCJSExport(existing);
17497
+ if (!wrapResult.wrapped) {
17498
+ return { modified: false, reason: "no-export" };
17499
+ }
17500
+ const modified = requireLine + "\n" + wrapResult.content;
17501
+ fs.writeFileSync(configPath, modified, "utf-8");
17502
+ return { modified: true };
17503
+ }
17504
+ function wrapExport(content) {
17505
+ const marker = "export default";
17506
+ const idx = content.lastIndexOf(marker);
17507
+ if (idx === -1) {
17508
+ return { content, wrapped: false };
17509
+ }
17510
+ const preamble = content.slice(0, idx);
17511
+ const exprRaw = content.slice(idx + marker.length);
17512
+ const expr = exprRaw.trim().replace(/;?\s*$/, "");
17513
+ if (expr.length === 0) {
17514
+ return { content, wrapped: false };
17515
+ }
17516
+ return {
17517
+ content: preamble + `export default withGlasstraceConfig(${expr});
17518
+ `,
17519
+ wrapped: true
17520
+ };
17521
+ }
17522
+ function wrapCJSExport(content) {
17523
+ const cjsMarker = "module.exports";
17524
+ const cjsIdx = content.lastIndexOf(cjsMarker);
17525
+ if (cjsIdx === -1) {
17526
+ return { content, wrapped: false };
17527
+ }
17528
+ const preamble = content.slice(0, cjsIdx);
17529
+ const afterMarker = content.slice(cjsIdx + cjsMarker.length);
17530
+ const eqMatch = /^\s*=\s*/.exec(afterMarker);
17531
+ if (!eqMatch) {
17532
+ return { content, wrapped: false };
17533
+ }
17534
+ const exprRaw = afterMarker.slice(eqMatch[0].length);
17535
+ const expr = exprRaw.trim().replace(/;?\s*$/, "");
17536
+ if (expr.length === 0) {
17537
+ return { content, wrapped: false };
17538
+ }
17539
+ return {
17540
+ content: preamble + `module.exports = withGlasstraceConfig(${expr});
17541
+ `,
17542
+ wrapped: true
17543
+ };
17544
+ }
17545
+ async function scaffoldEnvLocal(projectRoot) {
17546
+ const filePath = path.join(projectRoot, ".env.local");
17547
+ if (fs.existsSync(filePath)) {
17548
+ const existing = fs.readFileSync(filePath, "utf-8");
17549
+ if (/^\s*#?\s*GLASSTRACE_API_KEY\s*=/m.test(existing)) {
17550
+ return false;
17551
+ }
17552
+ const separator = existing.endsWith("\n") ? "" : "\n";
17553
+ fs.writeFileSync(filePath, existing + separator + "# GLASSTRACE_API_KEY=your_key_here\n", "utf-8");
17554
+ return true;
17555
+ }
17556
+ fs.writeFileSync(filePath, "# GLASSTRACE_API_KEY=your_key_here\n", "utf-8");
17557
+ return true;
17558
+ }
17559
+ async function addCoverageMapEnv(projectRoot) {
17560
+ const filePath = path.join(projectRoot, ".env.local");
17561
+ if (!fs.existsSync(filePath)) {
17562
+ fs.writeFileSync(filePath, "GLASSTRACE_COVERAGE_MAP=true\n", "utf-8");
17563
+ return true;
17564
+ }
17565
+ const existing = fs.readFileSync(filePath, "utf-8");
17566
+ const keyRegex = /^(\s*GLASSTRACE_COVERAGE_MAP\s*=\s*)(.*)$/m;
17567
+ const keyMatch = keyRegex.exec(existing);
17568
+ if (keyMatch) {
17569
+ const currentValue = keyMatch[2].trim();
17570
+ if (currentValue === "true") {
17571
+ return false;
17572
+ }
17573
+ const updated = existing.replace(keyRegex, `${keyMatch[1]}true`);
17574
+ fs.writeFileSync(filePath, updated, "utf-8");
17575
+ return true;
17576
+ }
17577
+ const separator = existing.endsWith("\n") ? "" : "\n";
17578
+ fs.writeFileSync(filePath, existing + separator + "GLASSTRACE_COVERAGE_MAP=true\n", "utf-8");
17579
+ return true;
17580
+ }
17581
+ async function scaffoldGitignore(projectRoot) {
17582
+ const filePath = path.join(projectRoot, ".gitignore");
17583
+ if (fs.existsSync(filePath)) {
17584
+ const existing = fs.readFileSync(filePath, "utf-8");
17585
+ const lines = existing.split("\n").map((l) => l.trim());
17586
+ if (lines.includes(".glasstrace/")) {
17587
+ return false;
17588
+ }
17589
+ const separator = existing.endsWith("\n") ? "" : "\n";
17590
+ fs.writeFileSync(filePath, existing + separator + ".glasstrace/\n", "utf-8");
17591
+ return true;
17592
+ }
17593
+ fs.writeFileSync(filePath, ".glasstrace/\n", "utf-8");
17594
+ return true;
17595
+ }
17596
+
17597
+ // src/cli/init.ts
17575
17598
  init_mcp_runtime();
17576
17599
 
17577
17600
  // src/import-graph.ts
@@ -18664,7 +18687,7 @@ async function verifyAnonKeyRegistration(projectRoot) {
18664
18687
  }
18665
18688
  const baseConfig = resolveConfig({ apiKey: devKey });
18666
18689
  const config2 = { ...baseConfig, apiKey: devKey };
18667
- const sdkVersion = true ? "1.1.1" : "0.0.0-dev";
18690
+ const sdkVersion = true ? "1.1.2" : "0.0.0-dev";
18668
18691
  const result = await verifyInitReachable(config2, anonKey, sdkVersion);
18669
18692
  if (result.ok) {
18670
18693
  return { outcome: "verified" };