@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
package/dist/cli/init.cjs CHANGED
@@ -39,547 +39,96 @@ function formatAgentName(name) {
39
39
  gemini: "Gemini",
40
40
  cursor: "Cursor",
41
41
  windsurf: "Windsurf",
42
- generic: "Generic"
42
+ generic: "Generic helper"
43
43
  };
44
44
  return displayNames[name];
45
45
  }
46
- var MCP_ENDPOINT, NEXT_CONFIG_NAMES;
46
+ var NEXT_CONFIG_NAMES;
47
47
  var init_constants = __esm({
48
48
  "src/cli/constants.ts"() {
49
49
  "use strict";
50
- MCP_ENDPOINT = "https://api.glasstrace.dev/mcp";
51
50
  NEXT_CONFIG_NAMES = ["next.config.ts", "next.config.js", "next.config.mjs"];
52
51
  }
53
52
  });
54
53
 
55
- // src/cli/scaffolder.ts
56
- function identityFingerprint(token) {
57
- return `sha256:${(0, import_node_crypto.createHash)("sha256").update(token).digest("hex")}`;
58
- }
59
- function hasRegisterGlasstraceCall(content) {
60
- return content.split("\n").some((line) => {
61
- const uncommented = line.replace(/\/\/.*$/, "");
62
- return /\bregisterGlasstrace\s*\(/.test(uncommented);
63
- });
64
- }
65
- function injectRegisterGlasstrace(content) {
66
- if (hasRegisterGlasstraceCall(content)) {
67
- return { injected: false, content };
68
- }
69
- const registerFnRegex = /export\s+(?:async\s+)?function\s+register\s*\([^)]*\)\s*\{/;
70
- const match = registerFnRegex.exec(content);
71
- if (!match) {
72
- return { injected: false, content };
73
- }
74
- const afterBrace = content.slice(match.index + match[0].length);
75
- const indentMatch = /\n([ \t]+)/.exec(afterBrace);
76
- const indent = indentMatch ? indentMatch[1] : " ";
77
- const importLine = 'import { registerGlasstrace } from "@glasstrace/sdk";\n';
78
- const hasGlasstraceImport2 = content.includes("@glasstrace/sdk");
79
- const insertPoint = match.index + match[0].length;
80
- const callInjection = `
81
- ${indent}// Glasstrace must be registered before other instrumentation
82
- ${indent}registerGlasstrace();
83
- `;
84
- let modified;
85
- if (hasGlasstraceImport2) {
86
- const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']@glasstrace\/sdk["']/;
87
- const importMatch = importRegex.exec(content);
88
- if (importMatch) {
89
- const specifiers = importMatch[1];
90
- const alreadyImported = specifiers.split(",").some((s) => s.trim() === "registerGlasstrace");
91
- if (alreadyImported) {
92
- modified = content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);
93
- } else {
94
- const existingImports = specifiers.trimEnd();
95
- const separator = existingImports.endsWith(",") ? " " : ", ";
96
- const updatedImport = `import { ${existingImports.trim()}${separator}registerGlasstrace } from "@glasstrace/sdk"`;
97
- modified = content.replace(importMatch[0], updatedImport);
98
- const newMatch = registerFnRegex.exec(modified);
99
- if (newMatch) {
100
- const newInsertPoint = newMatch.index + newMatch[0].length;
101
- modified = modified.slice(0, newInsertPoint) + callInjection + modified.slice(newInsertPoint);
102
- }
103
- }
104
- } else {
105
- modified = importLine + content;
106
- const newMatch = registerFnRegex.exec(modified);
107
- if (newMatch) {
108
- const newInsertPoint = newMatch.index + newMatch[0].length;
109
- modified = modified.slice(0, newInsertPoint) + callInjection + modified.slice(newInsertPoint);
110
- }
54
+ // ../../node_modules/zod/v4/core/core.js
55
+ // @__NO_SIDE_EFFECTS__
56
+ function $constructor(name, initializer3, params) {
57
+ function init(inst, def) {
58
+ if (!inst._zod) {
59
+ Object.defineProperty(inst, "_zod", {
60
+ value: {
61
+ def,
62
+ constr: _,
63
+ traits: /* @__PURE__ */ new Set()
64
+ },
65
+ enumerable: false
66
+ });
111
67
  }
112
- } else {
113
- modified = importLine + content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);
114
- }
115
- return { injected: true, content: modified };
116
- }
117
- function resolveInstrumentationTarget(projectRoot) {
118
- const rootExisting = [];
119
- const srcExisting = [];
120
- for (const name of INSTRUMENTATION_FILENAMES) {
121
- const rootPath = path.join(projectRoot, name);
122
- if (isRegularFile(rootPath)) {
123
- rootExisting.push(rootPath);
68
+ if (inst._zod.traits.has(name)) {
69
+ return;
124
70
  }
125
- const srcPath = path.join(projectRoot, "src", name);
126
- if (isRegularFile(srcPath)) {
127
- srcExisting.push(srcPath);
71
+ inst._zod.traits.add(name);
72
+ initializer3(inst, def);
73
+ const proto = _.prototype;
74
+ const keys = Object.keys(proto);
75
+ for (let i = 0; i < keys.length; i++) {
76
+ const k = keys[i];
77
+ if (!(k in inst)) {
78
+ inst[k] = proto[k].bind(inst);
79
+ }
128
80
  }
129
81
  }
130
- const existing = [...rootExisting, ...srcExisting];
131
- if (rootExisting.length > 0 && srcExisting.length > 0) {
132
- return {
133
- target: null,
134
- layout: null,
135
- existing,
136
- rootExisting,
137
- srcExisting,
138
- conflict: true
139
- };
140
- }
141
- if (srcExisting.length > 0) {
142
- return {
143
- target: srcExisting[0],
144
- layout: "src",
145
- existing,
146
- rootExisting,
147
- srcExisting,
148
- conflict: false
149
- };
150
- }
151
- if (rootExisting.length > 0) {
152
- return {
153
- target: rootExisting[0],
154
- layout: "root",
155
- existing,
156
- rootExisting,
157
- srcExisting,
158
- conflict: false
159
- };
160
- }
161
- const srcDir = path.join(projectRoot, "src");
162
- const layout = isDirectory(srcDir) ? "src" : "root";
163
- const target = layout === "src" ? path.join(projectRoot, "src", "instrumentation.ts") : path.join(projectRoot, "instrumentation.ts");
164
- return {
165
- target,
166
- layout,
167
- existing,
168
- rootExisting,
169
- srcExisting,
170
- conflict: false
171
- };
172
- }
173
- function isDirectory(p) {
174
- try {
175
- return fs.statSync(p).isDirectory();
176
- } catch {
177
- return false;
82
+ const Parent = params?.Parent ?? Object;
83
+ class Definition extends Parent {
178
84
  }
179
- }
180
- function isRegularFile(p) {
181
- try {
182
- const stat2 = fs.lstatSync(p);
183
- if (stat2.isSymbolicLink()) {
184
- return fs.statSync(p).isFile();
85
+ Object.defineProperty(Definition, "name", { value: name });
86
+ function _(def) {
87
+ var _a2;
88
+ const inst = params?.Parent ? new Definition() : this;
89
+ init(inst, def);
90
+ (_a2 = inst._zod).deferred ?? (_a2.deferred = []);
91
+ for (const fn of inst._zod.deferred) {
92
+ fn();
185
93
  }
186
- return stat2.isFile();
187
- } catch {
188
- return false;
94
+ return inst;
189
95
  }
190
- }
191
- function appendRegisterFunction(content) {
192
- const importLine = 'import { registerGlasstrace } from "@glasstrace/sdk";\n';
193
- 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";
194
- let withImport = content;
195
- const hasGlasstraceImport2 = content.includes("@glasstrace/sdk");
196
- if (!hasGlasstraceImport2) {
197
- withImport = importLine + content;
198
- } else {
199
- const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']@glasstrace\/sdk["']/;
200
- const importMatch = importRegex.exec(content);
201
- if (importMatch) {
202
- const specifiers = importMatch[1];
203
- const alreadyImported = specifiers.split(",").some((s) => s.trim() === "registerGlasstrace");
204
- if (!alreadyImported) {
205
- const existingImports = specifiers.trimEnd();
206
- const separator = existingImports.endsWith(",") ? " " : ", ";
207
- const updatedImport = `import { ${existingImports.trim()}${separator}registerGlasstrace } from "@glasstrace/sdk"`;
208
- withImport = content.replace(importMatch[0], updatedImport);
209
- }
210
- } else {
211
- withImport = importLine + content;
96
+ Object.defineProperty(_, "init", { value: init });
97
+ Object.defineProperty(_, Symbol.hasInstance, {
98
+ value: (inst) => {
99
+ if (params?.Parent && inst instanceof params.Parent)
100
+ return true;
101
+ return inst?._zod?.traits?.has(name);
212
102
  }
213
- }
214
- const trailingNewline = withImport.endsWith("\n") ? "" : "\n";
215
- return withImport + trailingNewline + functionBlock;
216
- }
217
- async function defaultInstrumentationPrompt(question, defaultValue) {
218
- if (!process.stdin.isTTY) return defaultValue;
219
- const readline2 = await import("node:readline");
220
- const rl = readline2.createInterface({
221
- input: process.stdin,
222
- output: process.stdout
223
- });
224
- return new Promise((resolve2) => {
225
- const suffix = defaultValue ? " [Y/n] " : " [y/N] ";
226
- rl.question(question + suffix, (answer) => {
227
- rl.close();
228
- const trimmed = answer.trim().toLowerCase();
229
- if (trimmed === "") {
230
- resolve2(defaultValue);
231
- return;
232
- }
233
- resolve2(trimmed === "y" || trimmed === "yes");
234
- });
235
103
  });
104
+ Object.defineProperty(_, "name", { value: name });
105
+ return _;
236
106
  }
237
- async function scaffoldInstrumentation(projectRoot, options = {}) {
238
- const target = resolveInstrumentationTarget(projectRoot);
239
- if (target.conflict) {
240
- return {
241
- action: "conflict",
242
- // Point the user at the `src/` variant — modern Next.js apps with a
243
- // `src/` directory load from there, so that's the merge target. The
244
- // competing path is reported separately for the error message.
245
- filePath: target.srcExisting[0],
246
- conflictingPath: target.rootExisting[0]
107
+ function config(newConfig) {
108
+ if (newConfig)
109
+ Object.assign(globalConfig, newConfig);
110
+ return globalConfig;
111
+ }
112
+ var NEVER, $brand, $ZodAsyncError, $ZodEncodeError, globalConfig;
113
+ var init_core = __esm({
114
+ "../../node_modules/zod/v4/core/core.js"() {
115
+ "use strict";
116
+ NEVER = Object.freeze({
117
+ status: "aborted"
118
+ });
119
+ $brand = /* @__PURE__ */ Symbol("zod_brand");
120
+ $ZodAsyncError = class extends Error {
121
+ constructor() {
122
+ super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);
123
+ }
247
124
  };
248
- }
249
- const filePath = target.target;
250
- const layout = target.layout;
251
- if (filePath === null || layout === null) {
252
- return { action: "unrecognized" };
253
- }
254
- const force = options.force === true;
255
- const prompt = options.prompt ?? defaultInstrumentationPrompt;
256
- if (!fs.existsSync(filePath)) {
257
- const content = `import { registerGlasstrace } from "@glasstrace/sdk";
258
-
259
- export async function register() {
260
- // Glasstrace must be registered before Prisma instrumentation
261
- // to ensure all ORM spans are captured correctly.
262
- // If you use @prisma/instrumentation, import it after this call.
263
- registerGlasstrace();
264
- }
265
- `;
266
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
267
- fs.writeFileSync(filePath, content, "utf-8");
268
- return { action: "created", filePath, layout };
269
- }
270
- const existing = fs.readFileSync(filePath, "utf-8");
271
- if (hasRegisterGlasstraceCall(existing)) {
272
- return { action: "already-registered", filePath, layout };
273
- }
274
- if (!force) {
275
- const approved = await prompt(
276
- `Merge registerGlasstrace() into ${path.relative(projectRoot, filePath)}?`,
277
- false
278
- );
279
- if (!approved) {
280
- return { action: "skipped", filePath, layout };
281
- }
282
- }
283
- const injectResult = injectRegisterGlasstrace(existing);
284
- if (injectResult.injected) {
285
- fs.writeFileSync(filePath, injectResult.content, "utf-8");
286
- return { action: "injected", filePath, layout };
287
- }
288
- const appended = appendRegisterFunction(existing);
289
- fs.writeFileSync(filePath, appended, "utf-8");
290
- return { action: "appended", filePath, layout };
291
- }
292
- async function scaffoldNextConfig(projectRoot) {
293
- let configPath;
294
- let configName;
295
- for (const name of NEXT_CONFIG_NAMES) {
296
- const candidate = path.join(projectRoot, name);
297
- if (fs.existsSync(candidate)) {
298
- configPath = candidate;
299
- configName = name;
300
- break;
301
- }
302
- }
303
- if (configPath === void 0 || configName === void 0) {
304
- return null;
305
- }
306
- const existing = fs.readFileSync(configPath, "utf-8");
307
- if (existing.trim().length === 0) {
308
- return { modified: false, reason: "empty-file" };
309
- }
310
- if (existing.includes("withGlasstraceConfig")) {
311
- return { modified: false, reason: "already-wrapped" };
312
- }
313
- const isESM = configName.endsWith(".ts") || configName.endsWith(".mjs");
314
- if (isESM) {
315
- const importLine = 'import { withGlasstraceConfig } from "@glasstrace/sdk";\n';
316
- const wrapResult2 = wrapExport(existing);
317
- if (!wrapResult2.wrapped) {
318
- return { modified: false, reason: "no-export" };
319
- }
320
- const modified2 = importLine + "\n" + wrapResult2.content;
321
- fs.writeFileSync(configPath, modified2, "utf-8");
322
- return { modified: true };
323
- }
324
- const requireLine = 'const { withGlasstraceConfig } = require("@glasstrace/sdk");\n';
325
- const wrapResult = wrapCJSExport(existing);
326
- if (!wrapResult.wrapped) {
327
- return { modified: false, reason: "no-export" };
328
- }
329
- const modified = requireLine + "\n" + wrapResult.content;
330
- fs.writeFileSync(configPath, modified, "utf-8");
331
- return { modified: true };
332
- }
333
- function wrapExport(content) {
334
- const marker = "export default";
335
- const idx = content.lastIndexOf(marker);
336
- if (idx === -1) {
337
- return { content, wrapped: false };
338
- }
339
- const preamble = content.slice(0, idx);
340
- const exprRaw = content.slice(idx + marker.length);
341
- const expr = exprRaw.trim().replace(/;?\s*$/, "");
342
- if (expr.length === 0) {
343
- return { content, wrapped: false };
344
- }
345
- return {
346
- content: preamble + `export default withGlasstraceConfig(${expr});
347
- `,
348
- wrapped: true
349
- };
350
- }
351
- function wrapCJSExport(content) {
352
- const cjsMarker = "module.exports";
353
- const cjsIdx = content.lastIndexOf(cjsMarker);
354
- if (cjsIdx === -1) {
355
- return { content, wrapped: false };
356
- }
357
- const preamble = content.slice(0, cjsIdx);
358
- const afterMarker = content.slice(cjsIdx + cjsMarker.length);
359
- const eqMatch = /^\s*=\s*/.exec(afterMarker);
360
- if (!eqMatch) {
361
- return { content, wrapped: false };
362
- }
363
- const exprRaw = afterMarker.slice(eqMatch[0].length);
364
- const expr = exprRaw.trim().replace(/;?\s*$/, "");
365
- if (expr.length === 0) {
366
- return { content, wrapped: false };
367
- }
368
- return {
369
- content: preamble + `module.exports = withGlasstraceConfig(${expr});
370
- `,
371
- wrapped: true
372
- };
373
- }
374
- function readEnvLocalApiKey(content) {
375
- let last = null;
376
- const regex = /^\s*GLASSTRACE_API_KEY\s*=\s*(.*)$/gm;
377
- let match;
378
- while ((match = regex.exec(content)) !== null) {
379
- const raw = match[1].trim();
380
- if (raw === "") continue;
381
- const unquoted = raw.replace(/^(['"])(.*)\1$/, "$2");
382
- if (unquoted === "" || unquoted === "your_key_here") continue;
383
- last = unquoted;
384
- }
385
- return last;
386
- }
387
- function isDevApiKey(value) {
388
- if (value === null || value === void 0) return false;
389
- return value.trim().startsWith("gt_dev_");
390
- }
391
- async function scaffoldEnvLocal(projectRoot) {
392
- const filePath = path.join(projectRoot, ".env.local");
393
- if (fs.existsSync(filePath)) {
394
- const existing = fs.readFileSync(filePath, "utf-8");
395
- if (/^\s*#?\s*GLASSTRACE_API_KEY\s*=/m.test(existing)) {
396
- return false;
397
- }
398
- const separator = existing.endsWith("\n") ? "" : "\n";
399
- fs.writeFileSync(filePath, existing + separator + "# GLASSTRACE_API_KEY=your_key_here\n", "utf-8");
400
- return true;
401
- }
402
- fs.writeFileSync(filePath, "# GLASSTRACE_API_KEY=your_key_here\n", "utf-8");
403
- return true;
404
- }
405
- async function addCoverageMapEnv(projectRoot) {
406
- const filePath = path.join(projectRoot, ".env.local");
407
- if (!fs.existsSync(filePath)) {
408
- fs.writeFileSync(filePath, "GLASSTRACE_COVERAGE_MAP=true\n", "utf-8");
409
- return true;
410
- }
411
- const existing = fs.readFileSync(filePath, "utf-8");
412
- const keyRegex = /^(\s*GLASSTRACE_COVERAGE_MAP\s*=\s*)(.*)$/m;
413
- const keyMatch = keyRegex.exec(existing);
414
- if (keyMatch) {
415
- const currentValue = keyMatch[2].trim();
416
- if (currentValue === "true") {
417
- return false;
418
- }
419
- const updated = existing.replace(keyRegex, `${keyMatch[1]}true`);
420
- fs.writeFileSync(filePath, updated, "utf-8");
421
- return true;
422
- }
423
- const separator = existing.endsWith("\n") ? "" : "\n";
424
- fs.writeFileSync(filePath, existing + separator + "GLASSTRACE_COVERAGE_MAP=true\n", "utf-8");
425
- return true;
426
- }
427
- async function scaffoldGitignore(projectRoot) {
428
- const filePath = path.join(projectRoot, ".gitignore");
429
- if (fs.existsSync(filePath)) {
430
- const existing = fs.readFileSync(filePath, "utf-8");
431
- const lines = existing.split("\n").map((l) => l.trim());
432
- if (lines.includes(".glasstrace/")) {
433
- return false;
434
- }
435
- const separator = existing.endsWith("\n") ? "" : "\n";
436
- fs.writeFileSync(filePath, existing + separator + ".glasstrace/\n", "utf-8");
437
- return true;
438
- }
439
- fs.writeFileSync(filePath, ".glasstrace/\n", "utf-8");
440
- return true;
441
- }
442
- function mcpConfigMatches(existingContent, expectedContent) {
443
- const trimmedExpected = expectedContent.trim();
444
- try {
445
- const existingParsed = JSON.parse(existingContent);
446
- const expectedParsed = JSON.parse(trimmedExpected);
447
- return JSON.stringify(canonicalize(existingParsed)) === JSON.stringify(canonicalize(expectedParsed));
448
- } catch {
449
- }
450
- return existingContent.trim() === trimmedExpected;
451
- }
452
- function canonicalize(value) {
453
- if (Array.isArray(value)) {
454
- return value.map(canonicalize);
455
- }
456
- if (value !== null && typeof value === "object") {
457
- const obj = value;
458
- const sorted = {};
459
- for (const key of Object.keys(obj).sort()) {
460
- sorted[key] = canonicalize(obj[key]);
461
- }
462
- return sorted;
463
- }
464
- return value;
465
- }
466
- async function scaffoldMcpMarker(projectRoot, anonKey) {
467
- const dirPath = path.join(projectRoot, ".glasstrace");
468
- const markerPath = path.join(dirPath, "mcp-connected");
469
- const keyHash = identityFingerprint(anonKey);
470
- if (fs.existsSync(markerPath)) {
471
- try {
472
- const existing = JSON.parse(fs.readFileSync(markerPath, "utf-8"));
473
- if (existing.keyHash === keyHash) {
474
- return false;
475
- }
476
- } catch {
477
- }
478
- }
479
- fs.mkdirSync(dirPath, { recursive: true, mode: 448 });
480
- const marker = JSON.stringify(
481
- { keyHash, configuredAt: (/* @__PURE__ */ new Date()).toISOString() },
482
- null,
483
- 2
484
- );
485
- fs.writeFileSync(markerPath, marker, { mode: 384 });
486
- fs.chmodSync(markerPath, 384);
487
- return true;
488
- }
489
- var import_node_crypto, fs, path, INSTRUMENTATION_FILENAMES;
490
- var init_scaffolder = __esm({
491
- "src/cli/scaffolder.ts"() {
492
- "use strict";
493
- import_node_crypto = require("node:crypto");
494
- fs = __toESM(require("node:fs"), 1);
495
- path = __toESM(require("node:path"), 1);
496
- init_constants();
497
- INSTRUMENTATION_FILENAMES = [
498
- "instrumentation.ts",
499
- "instrumentation.js",
500
- "instrumentation.mjs"
501
- ];
502
- }
503
- });
504
-
505
- // ../../node_modules/zod/v4/core/core.js
506
- // @__NO_SIDE_EFFECTS__
507
- function $constructor(name, initializer3, params) {
508
- function init(inst, def) {
509
- if (!inst._zod) {
510
- Object.defineProperty(inst, "_zod", {
511
- value: {
512
- def,
513
- constr: _,
514
- traits: /* @__PURE__ */ new Set()
515
- },
516
- enumerable: false
517
- });
518
- }
519
- if (inst._zod.traits.has(name)) {
520
- return;
521
- }
522
- inst._zod.traits.add(name);
523
- initializer3(inst, def);
524
- const proto = _.prototype;
525
- const keys = Object.keys(proto);
526
- for (let i = 0; i < keys.length; i++) {
527
- const k = keys[i];
528
- if (!(k in inst)) {
529
- inst[k] = proto[k].bind(inst);
530
- }
531
- }
532
- }
533
- const Parent = params?.Parent ?? Object;
534
- class Definition extends Parent {
535
- }
536
- Object.defineProperty(Definition, "name", { value: name });
537
- function _(def) {
538
- var _a2;
539
- const inst = params?.Parent ? new Definition() : this;
540
- init(inst, def);
541
- (_a2 = inst._zod).deferred ?? (_a2.deferred = []);
542
- for (const fn of inst._zod.deferred) {
543
- fn();
544
- }
545
- return inst;
546
- }
547
- Object.defineProperty(_, "init", { value: init });
548
- Object.defineProperty(_, Symbol.hasInstance, {
549
- value: (inst) => {
550
- if (params?.Parent && inst instanceof params.Parent)
551
- return true;
552
- return inst?._zod?.traits?.has(name);
553
- }
554
- });
555
- Object.defineProperty(_, "name", { value: name });
556
- return _;
557
- }
558
- function config(newConfig) {
559
- if (newConfig)
560
- Object.assign(globalConfig, newConfig);
561
- return globalConfig;
562
- }
563
- var NEVER, $brand, $ZodAsyncError, $ZodEncodeError, globalConfig;
564
- var init_core = __esm({
565
- "../../node_modules/zod/v4/core/core.js"() {
566
- "use strict";
567
- NEVER = Object.freeze({
568
- status: "aborted"
569
- });
570
- $brand = /* @__PURE__ */ Symbol("zod_brand");
571
- $ZodAsyncError = class extends Error {
572
- constructor() {
573
- super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);
574
- }
575
- };
576
- $ZodEncodeError = class extends Error {
577
- constructor(name) {
578
- super(`Encountered unidirectional transform during encode: ${name}`);
579
- this.name = "ZodEncodeError";
580
- }
581
- };
582
- globalConfig = {};
125
+ $ZodEncodeError = class extends Error {
126
+ constructor(name) {
127
+ super(`Encountered unidirectional transform during encode: ${name}`);
128
+ this.name = "ZodEncodeError";
129
+ }
130
+ };
131
+ globalConfig = {};
583
132
  }
584
133
  });
585
134
 
@@ -15141,6 +14690,22 @@ async function readAnonKey(projectRoot) {
15141
14690
  }
15142
14691
  return null;
15143
14692
  }
14693
+ async function readClaimedKey(projectRoot) {
14694
+ const root = projectRoot ?? process.cwd();
14695
+ const modules = await loadFsPath();
14696
+ if (!modules) return null;
14697
+ const keyPath = modules.path.join(root, GLASSTRACE_DIR, CLAIMED_KEY_FILE);
14698
+ try {
14699
+ const content = await modules.fs.readFile(keyPath, "utf-8");
14700
+ const trimmed = content.trim();
14701
+ const parsed = DevApiKeySchema.safeParse(trimmed);
14702
+ if (parsed.success) {
14703
+ return parsed.data;
14704
+ }
14705
+ } catch {
14706
+ }
14707
+ return null;
14708
+ }
15144
14709
  async function getOrCreateAnonKey(projectRoot) {
15145
14710
  const root = projectRoot ?? process.cwd();
15146
14711
  const existingKey = await readAnonKey(root);
@@ -15182,21 +14747,208 @@ async function getOrCreateAnonKey(projectRoot) {
15182
14747
  } catch {
15183
14748
  }
15184
14749
  }
15185
- ephemeralKeyCache.set(root, newKey);
15186
- console.warn(
15187
- `[glasstrace] Failed to persist anonymous key to ${keyPath}: ${err instanceof Error ? err.message : String(err)}. Using ephemeral key.`
15188
- );
15189
- return newKey;
14750
+ ephemeralKeyCache.set(root, newKey);
14751
+ console.warn(
14752
+ `[glasstrace] Failed to persist anonymous key to ${keyPath}: ${err instanceof Error ? err.message : String(err)}. Using ephemeral key.`
14753
+ );
14754
+ return newKey;
14755
+ }
14756
+ }
14757
+ var GLASSTRACE_DIR, ANON_KEY_FILE, CLAIMED_KEY_FILE, fsPathCache, ephemeralKeyCache;
14758
+ var init_anon_key = __esm({
14759
+ "src/anon-key.ts"() {
14760
+ "use strict";
14761
+ init_dist();
14762
+ GLASSTRACE_DIR = ".glasstrace";
14763
+ ANON_KEY_FILE = "anon_key";
14764
+ CLAIMED_KEY_FILE = "claimed-key";
14765
+ ephemeralKeyCache = /* @__PURE__ */ new Map();
14766
+ }
14767
+ });
14768
+
14769
+ // src/mcp-runtime.ts
14770
+ async function loadFsPath2() {
14771
+ if (fsPathCache2 !== void 0) return fsPathCache2;
14772
+ try {
14773
+ const [fs10, path10] = await Promise.all([
14774
+ import("node:fs/promises"),
14775
+ import("node:path")
14776
+ ]);
14777
+ fsPathCache2 = { fs: fs10, path: path10 };
14778
+ return fsPathCache2;
14779
+ } catch {
14780
+ fsPathCache2 = null;
14781
+ return null;
14782
+ }
14783
+ }
14784
+ function identityFingerprint(token) {
14785
+ return `sha256:${(0, import_node_crypto.createHash)("sha256").update(token).digest("hex")}`;
14786
+ }
14787
+ function mcpConfigMatches(existingContent, expectedContent) {
14788
+ const trimmedExpected = expectedContent.trim();
14789
+ try {
14790
+ const existingParsed = JSON.parse(existingContent);
14791
+ const expectedParsed = JSON.parse(trimmedExpected);
14792
+ return JSON.stringify(canonicalize(existingParsed)) === JSON.stringify(canonicalize(expectedParsed));
14793
+ } catch {
14794
+ }
14795
+ return existingContent.trim() === trimmedExpected;
14796
+ }
14797
+ function canonicalize(value) {
14798
+ if (Array.isArray(value)) {
14799
+ return value.map(canonicalize);
14800
+ }
14801
+ if (value !== null && typeof value === "object") {
14802
+ const obj = value;
14803
+ const sorted = {};
14804
+ for (const key of Object.keys(obj).sort()) {
14805
+ sorted[key] = canonicalize(obj[key]);
14806
+ }
14807
+ return sorted;
14808
+ }
14809
+ return value;
14810
+ }
14811
+ function readEnvLocalApiKey(content) {
14812
+ let last = null;
14813
+ const regex = /^\s*GLASSTRACE_API_KEY\s*=\s*(.*)$/gm;
14814
+ let match;
14815
+ while ((match = regex.exec(content)) !== null) {
14816
+ const raw = match[1].trim();
14817
+ if (raw === "") continue;
14818
+ const unquoted = raw.replace(/^(['"])(.*)\1$/, "$2");
14819
+ if (unquoted === "" || unquoted === "your_key_here") continue;
14820
+ last = unquoted;
14821
+ }
14822
+ return last;
14823
+ }
14824
+ function isDevApiKey(value) {
14825
+ if (value === null || value === void 0) return false;
14826
+ return value.trim().startsWith("gt_dev_");
14827
+ }
14828
+ function isAnonApiKey(value) {
14829
+ if (value === null || value === void 0) return false;
14830
+ return AnonApiKeySchema.safeParse(value).success;
14831
+ }
14832
+ async function resolveEffectiveMcpCredential(projectRoot) {
14833
+ const root = projectRoot ?? process.cwd();
14834
+ const warnings = [];
14835
+ const envLocalKey = await readEnvLocalDevKey(root, warnings);
14836
+ const claimedKey = envLocalKey === null ? await readClaimedKey(root) : null;
14837
+ const anonKey = await readAnonKey(root);
14838
+ let effective = null;
14839
+ if (envLocalKey !== null) {
14840
+ effective = { source: "env-local", key: envLocalKey };
14841
+ } else if (claimedKey !== null) {
14842
+ effective = { source: "claimed-key", key: claimedKey };
14843
+ warnings.push("claimed-key-only");
14844
+ } else if (anonKey !== null) {
14845
+ effective = { source: "anon", key: anonKey };
14846
+ }
14847
+ return { effective, anonKey, warnings };
14848
+ }
14849
+ async function readEnvLocalDevKey(root, warnings) {
14850
+ const modules = await loadFsPath2();
14851
+ if (!modules) return null;
14852
+ const envPath = modules.path.join(root, ".env.local");
14853
+ let content;
14854
+ try {
14855
+ content = await modules.fs.readFile(envPath, "utf-8");
14856
+ } catch {
14857
+ return null;
14858
+ }
14859
+ const raw = readEnvLocalApiKey(content);
14860
+ if (raw === null) return null;
14861
+ const parsed = DevApiKeySchema.safeParse(raw);
14862
+ if (!parsed.success) {
14863
+ warnings.push("malformed-env-local");
14864
+ return null;
14865
+ }
14866
+ return parsed.data;
14867
+ }
14868
+ async function readMcpMarker(projectRoot) {
14869
+ const root = projectRoot ?? process.cwd();
14870
+ const modules = await loadFsPath2();
14871
+ if (!modules) return { status: "absent" };
14872
+ const markerPath = modules.path.join(root, GLASSTRACE_DIR2, MCP_MARKER_FILE);
14873
+ let content;
14874
+ try {
14875
+ content = await modules.fs.readFile(markerPath, "utf-8");
14876
+ } catch {
14877
+ return { status: "absent" };
14878
+ }
14879
+ let parsed;
14880
+ try {
14881
+ parsed = JSON.parse(content);
14882
+ } catch {
14883
+ return { status: "corrupted" };
14884
+ }
14885
+ if (parsed === null || typeof parsed !== "object") {
14886
+ return { status: "corrupted" };
14887
+ }
14888
+ const obj = parsed;
14889
+ const version2 = obj["version"];
14890
+ if (version2 === void 0) {
14891
+ const keyHash = obj["keyHash"];
14892
+ if (typeof keyHash !== "string" || keyHash === "") {
14893
+ return { status: "corrupted" };
14894
+ }
14895
+ return {
14896
+ status: "valid",
14897
+ credentialSource: "anon",
14898
+ credentialHash: keyHash
14899
+ };
14900
+ }
14901
+ if (version2 === 2) {
14902
+ const source = obj["credentialSource"];
14903
+ const hash2 = obj["credentialHash"];
14904
+ if (source !== "env-local" && source !== "claimed-key" && source !== "anon" || typeof hash2 !== "string" || hash2 === "") {
14905
+ return { status: "corrupted" };
14906
+ }
14907
+ return {
14908
+ status: "valid",
14909
+ credentialSource: source,
14910
+ credentialHash: hash2
14911
+ };
14912
+ }
14913
+ if (typeof version2 === "number" && version2 > 2) {
14914
+ return { status: "unknown-version" };
15190
14915
  }
14916
+ return { status: "corrupted" };
15191
14917
  }
15192
- var GLASSTRACE_DIR, ANON_KEY_FILE, fsPathCache, ephemeralKeyCache;
15193
- var init_anon_key = __esm({
15194
- "src/anon-key.ts"() {
14918
+ async function writeMcpMarker(projectRoot, target) {
14919
+ const modules = await loadFsPath2();
14920
+ if (!modules) return false;
14921
+ const dirPath = modules.path.join(projectRoot, GLASSTRACE_DIR2);
14922
+ const markerPath = modules.path.join(dirPath, MCP_MARKER_FILE);
14923
+ const state = await readMcpMarker(projectRoot);
14924
+ if (state.status === "valid" && state.credentialSource === target.credentialSource && state.credentialHash === target.credentialHash) {
14925
+ return false;
14926
+ }
14927
+ await modules.fs.mkdir(dirPath, { recursive: true, mode: 448 });
14928
+ const body = JSON.stringify(
14929
+ {
14930
+ version: 2,
14931
+ credentialSource: target.credentialSource,
14932
+ credentialHash: target.credentialHash,
14933
+ configuredAt: (/* @__PURE__ */ new Date()).toISOString()
14934
+ },
14935
+ null,
14936
+ 2
14937
+ );
14938
+ await modules.fs.writeFile(markerPath, body, { mode: 384 });
14939
+ await modules.fs.chmod(markerPath, 384);
14940
+ return true;
14941
+ }
14942
+ var import_node_crypto, MCP_ENDPOINT, fsPathCache2, MCP_MARKER_FILE, GLASSTRACE_DIR2;
14943
+ var init_mcp_runtime = __esm({
14944
+ "src/mcp-runtime.ts"() {
15195
14945
  "use strict";
14946
+ import_node_crypto = require("node:crypto");
15196
14947
  init_dist();
15197
- GLASSTRACE_DIR = ".glasstrace";
15198
- ANON_KEY_FILE = "anon_key";
15199
- ephemeralKeyCache = /* @__PURE__ */ new Map();
14948
+ init_anon_key();
14949
+ MCP_ENDPOINT = "https://api.glasstrace.dev/mcp";
14950
+ MCP_MARKER_FILE = "mcp-connected";
14951
+ GLASSTRACE_DIR2 = ".glasstrace";
15200
14952
  }
15201
14953
  });
15202
14954
 
@@ -15360,12 +15112,12 @@ var init_detect = __esm({
15360
15112
  });
15361
15113
 
15362
15114
  // src/agent-detection/configs.ts
15363
- function generateMcpConfig(agent, endpoint, anonKey) {
15115
+ function generateMcpConfig(agent, endpoint, bearer) {
15364
15116
  if (!endpoint || endpoint.trim() === "") {
15365
15117
  throw new Error("endpoint must not be empty");
15366
15118
  }
15367
- if (!anonKey || anonKey.trim() === "") {
15368
- throw new Error("anonKey must not be empty");
15119
+ if (!bearer || bearer.trim() === "") {
15120
+ throw new Error("bearer must not be empty");
15369
15121
  }
15370
15122
  switch (agent.name) {
15371
15123
  case "claude":
@@ -15376,7 +15128,7 @@ function generateMcpConfig(agent, endpoint, anonKey) {
15376
15128
  type: "http",
15377
15129
  url: endpoint,
15378
15130
  headers: {
15379
- Authorization: `Bearer ${anonKey}`
15131
+ Authorization: `Bearer ${bearer}`
15380
15132
  }
15381
15133
  }
15382
15134
  }
@@ -15400,7 +15152,7 @@ function generateMcpConfig(agent, endpoint, anonKey) {
15400
15152
  glasstrace: {
15401
15153
  httpUrl: endpoint,
15402
15154
  headers: {
15403
- Authorization: `Bearer ${anonKey}`
15155
+ Authorization: `Bearer ${bearer}`
15404
15156
  }
15405
15157
  }
15406
15158
  }
@@ -15415,7 +15167,7 @@ function generateMcpConfig(agent, endpoint, anonKey) {
15415
15167
  glasstrace: {
15416
15168
  url: endpoint,
15417
15169
  headers: {
15418
- Authorization: `Bearer ${anonKey}`
15170
+ Authorization: `Bearer ${bearer}`
15419
15171
  }
15420
15172
  }
15421
15173
  }
@@ -15430,7 +15182,7 @@ function generateMcpConfig(agent, endpoint, anonKey) {
15430
15182
  glasstrace: {
15431
15183
  serverUrl: endpoint,
15432
15184
  headers: {
15433
- Authorization: `Bearer ${anonKey}`
15185
+ Authorization: `Bearer ${bearer}`
15434
15186
  }
15435
15187
  }
15436
15188
  }
@@ -15445,7 +15197,7 @@ function generateMcpConfig(agent, endpoint, anonKey) {
15445
15197
  glasstrace: {
15446
15198
  url: endpoint,
15447
15199
  headers: {
15448
- Authorization: `Bearer ${anonKey}`
15200
+ Authorization: `Bearer ${bearer}`
15449
15201
  }
15450
15202
  }
15451
15203
  }
@@ -16743,677 +16495,1107 @@ async function runUninit(options) {
16743
16495
  }
16744
16496
  }
16745
16497
  }
16746
- } catch (err) {
16747
- errors.push(
16748
- `Failed to process .gitignore: ${err instanceof Error ? err.message : String(err)}`
16498
+ } catch (err) {
16499
+ errors.push(
16500
+ `Failed to process .gitignore: ${err instanceof Error ? err.message : String(err)}`
16501
+ );
16502
+ }
16503
+ try {
16504
+ for (const configFile of MCP_CONFIG_FILES) {
16505
+ const configPath = path5.join(projectRoot, configFile);
16506
+ if (!fs5.existsSync(configPath)) {
16507
+ continue;
16508
+ }
16509
+ const content = fs5.readFileSync(configPath, "utf-8");
16510
+ const result = processJsonMcpConfig(content);
16511
+ if (result.action === "deleted") {
16512
+ if (!dryRun) {
16513
+ fs5.unlinkSync(configPath);
16514
+ }
16515
+ summary.push(`${prefix}Deleted ${configFile}`);
16516
+ } else if (result.action === "removed-key" && result.content !== void 0) {
16517
+ if (!dryRun) {
16518
+ fs5.writeFileSync(configPath, result.content, "utf-8");
16519
+ }
16520
+ summary.push(`${prefix}Removed glasstrace from ${configFile}`);
16521
+ }
16522
+ }
16523
+ const codexConfigPath = path5.join(projectRoot, ".codex", "config.toml");
16524
+ if (fs5.existsSync(codexConfigPath)) {
16525
+ const content = fs5.readFileSync(codexConfigPath, "utf-8");
16526
+ const tomlResult = processTomlMcpConfig(content);
16527
+ if (tomlResult.action === "deleted") {
16528
+ if (!dryRun) {
16529
+ fs5.unlinkSync(codexConfigPath);
16530
+ }
16531
+ summary.push(`${prefix}Deleted .codex/config.toml`);
16532
+ } else if (tomlResult.action === "removed-section" && tomlResult.content !== void 0) {
16533
+ if (!dryRun) {
16534
+ fs5.writeFileSync(codexConfigPath, tomlResult.content, "utf-8");
16535
+ }
16536
+ summary.push(`${prefix}Removed glasstrace from .codex/config.toml`);
16537
+ }
16538
+ }
16539
+ const hasWindsurfMarkers = fs5.existsSync(path5.join(projectRoot, ".windsurfrules")) || fs5.existsSync(path5.join(projectRoot, ".windsurf"));
16540
+ if (hasWindsurfMarkers) {
16541
+ const windsurfConfigPath = path5.join(
16542
+ os.homedir(),
16543
+ ".codeium",
16544
+ "windsurf",
16545
+ "mcp_config.json"
16546
+ );
16547
+ if (fs5.existsSync(windsurfConfigPath)) {
16548
+ const content = fs5.readFileSync(windsurfConfigPath, "utf-8");
16549
+ const windsurfResult = processJsonMcpConfig(content);
16550
+ const home = os.homedir();
16551
+ const displayPath = windsurfConfigPath.startsWith(home) ? "~" + windsurfConfigPath.slice(home.length) : windsurfConfigPath;
16552
+ if (windsurfResult.action === "deleted") {
16553
+ if (!dryRun) {
16554
+ fs5.unlinkSync(windsurfConfigPath);
16555
+ }
16556
+ summary.push(
16557
+ `${prefix}Deleted global Windsurf config (${displayPath})`
16558
+ );
16559
+ } else if (windsurfResult.action === "removed-key" && windsurfResult.content !== void 0) {
16560
+ if (!dryRun) {
16561
+ fs5.writeFileSync(windsurfConfigPath, windsurfResult.content, "utf-8");
16562
+ }
16563
+ summary.push(
16564
+ `${prefix}Removed glasstrace from global Windsurf config (${displayPath})`
16565
+ );
16566
+ }
16567
+ }
16568
+ }
16569
+ } catch (err) {
16570
+ errors.push(
16571
+ `Failed to process MCP config: ${err instanceof Error ? err.message : String(err)}`
16572
+ );
16573
+ }
16574
+ try {
16575
+ for (const infoFile of AGENT_INFO_FILES) {
16576
+ const filePath = path5.join(projectRoot, infoFile);
16577
+ if (!fs5.existsSync(filePath)) {
16578
+ continue;
16579
+ }
16580
+ const content = fs5.readFileSync(filePath, "utf-8");
16581
+ const result = removeMarkerSection(content);
16582
+ if (result.removed) {
16583
+ if (result.content.trim().length === 0) {
16584
+ if (!dryRun) {
16585
+ fs5.unlinkSync(filePath);
16586
+ }
16587
+ summary.push(`${prefix}Deleted ${infoFile} (only contained Glasstrace section)`);
16588
+ } else {
16589
+ if (!dryRun) {
16590
+ fs5.writeFileSync(filePath, result.content, "utf-8");
16591
+ }
16592
+ summary.push(`${prefix}Removed Glasstrace section from ${infoFile}`);
16593
+ }
16594
+ }
16595
+ }
16596
+ } catch (err) {
16597
+ errors.push(
16598
+ `Failed to process agent info files: ${err instanceof Error ? err.message : String(err)}`
16599
+ );
16600
+ }
16601
+ if (summary.length === 0 && errors.length === 0) {
16602
+ summary.push("No Glasstrace artifacts found \u2014 nothing to do.");
16603
+ }
16604
+ return { exitCode: errors.length > 0 ? 1 : 0, summary, warnings, errors };
16605
+ }
16606
+ var fs5, os, path5, MCP_CONFIG_FILES, AGENT_INFO_FILES;
16607
+ var init_uninit = __esm({
16608
+ "src/cli/uninit.ts"() {
16609
+ "use strict";
16610
+ fs5 = __toESM(require("node:fs"), 1);
16611
+ os = __toESM(require("node:os"), 1);
16612
+ path5 = __toESM(require("node:path"), 1);
16613
+ init_constants();
16614
+ init_mcp_runtime();
16615
+ init_discovery_file();
16616
+ MCP_CONFIG_FILES = [".mcp.json", ".cursor/mcp.json", ".gemini/settings.json"];
16617
+ AGENT_INFO_FILES = [
16618
+ "CLAUDE.md",
16619
+ "codex.md",
16620
+ ".cursorrules"
16621
+ ];
16622
+ }
16623
+ });
16624
+
16625
+ // src/cli/mcp-add.ts
16626
+ var mcp_add_exports = {};
16627
+ __export(mcp_add_exports, {
16628
+ mcpAdd: () => mcpAdd,
16629
+ registerViaCli: () => registerViaCli
16630
+ });
16631
+ async function registerViaCli(agent, bearer) {
16632
+ if (!agent.cliAvailable) {
16633
+ return false;
16634
+ }
16635
+ if (agent.name !== "codex" && !isAnonApiKey(bearer)) {
16636
+ return false;
16637
+ }
16638
+ try {
16639
+ switch (agent.name) {
16640
+ case "claude": {
16641
+ const payload = JSON.stringify({
16642
+ type: "http",
16643
+ url: MCP_ENDPOINT,
16644
+ headers: { Authorization: `Bearer ${bearer}` }
16645
+ });
16646
+ await execFileAsync("claude", [
16647
+ "mcp",
16648
+ "add-json",
16649
+ "glasstrace",
16650
+ payload,
16651
+ "--scope",
16652
+ "project"
16653
+ ]);
16654
+ return true;
16655
+ }
16656
+ case "codex": {
16657
+ await execFileAsync("codex", [
16658
+ "mcp",
16659
+ "add",
16660
+ "glasstrace",
16661
+ "--url",
16662
+ MCP_ENDPOINT
16663
+ ]);
16664
+ const configPath = agent.mcpConfigPath;
16665
+ if (configPath !== null && fs6.existsSync(configPath)) {
16666
+ const content = fs6.readFileSync(configPath, "utf-8");
16667
+ if (!content.includes("bearer_token_env_var")) {
16668
+ const appendContent = content.endsWith("\n") ? "" : "\n";
16669
+ fs6.writeFileSync(
16670
+ configPath,
16671
+ content + appendContent + 'bearer_token_env_var = "GLASSTRACE_API_KEY"\n',
16672
+ "utf-8"
16673
+ );
16674
+ }
16675
+ }
16676
+ process.stderr.write(
16677
+ " Note: Set GLASSTRACE_API_KEY environment variable for Codex authentication.\n"
16678
+ );
16679
+ return true;
16680
+ }
16681
+ case "gemini": {
16682
+ await execFileAsync("gemini", [
16683
+ "mcp",
16684
+ "add",
16685
+ "--transport",
16686
+ "http",
16687
+ "--header",
16688
+ `Authorization: Bearer ${bearer}`,
16689
+ "glasstrace",
16690
+ MCP_ENDPOINT
16691
+ ]);
16692
+ return true;
16693
+ }
16694
+ default:
16695
+ return false;
16696
+ }
16697
+ } catch {
16698
+ return false;
16699
+ }
16700
+ }
16701
+ async function markerMatchesEffective(projectRoot, effective) {
16702
+ const state = await readMcpMarker(projectRoot);
16703
+ if (state.status !== "valid") return false;
16704
+ return state.credentialHash === identityFingerprint(effective.key);
16705
+ }
16706
+ async function mcpAdd(options) {
16707
+ const force = options?.force ?? false;
16708
+ const dryRun = options?.dryRun ?? false;
16709
+ const projectRoot = process.cwd();
16710
+ const messages = [];
16711
+ const resolved = await resolveEffectiveMcpCredential(projectRoot);
16712
+ if (resolved.effective === null) {
16713
+ return {
16714
+ exitCode: 1,
16715
+ results: [],
16716
+ messages: ["Error: Run `glasstrace init` first to generate an API key."]
16717
+ };
16718
+ }
16719
+ if (resolved.warnings.includes("claimed-key-only")) {
16720
+ messages.push(
16721
+ "Note: dev key was loaded from .glasstrace/claimed-key. Copy it into .env.local so your app and Codex pick it up automatically."
16722
+ );
16723
+ }
16724
+ const markerPath = path6.join(projectRoot, ".glasstrace", "mcp-connected");
16725
+ if (fs6.existsSync(markerPath) && !force) {
16726
+ if (await markerMatchesEffective(projectRoot, resolved.effective)) {
16727
+ return {
16728
+ exitCode: 0,
16729
+ results: [],
16730
+ messages: ["MCP already configured. Use --force to reconfigure."]
16731
+ };
16732
+ }
16733
+ messages.push(
16734
+ "Detected a credential change since MCP was last configured. Refreshing MCP config so queries use the current account credential."
16735
+ );
16736
+ }
16737
+ const agents = await detectAgents(projectRoot);
16738
+ const detectedNonGeneric = agents.filter((a) => a.name !== "generic");
16739
+ const genericAgent = agents.find((a) => a.name === "generic");
16740
+ const targetAgents = genericAgent ? [...detectedNonGeneric, genericAgent] : detectedNonGeneric;
16741
+ if (dryRun) {
16742
+ messages.push("Dry run: would perform the following actions:", "");
16743
+ for (const agent of targetAgents) {
16744
+ const name = formatAgentName(agent.name);
16745
+ if (agent.cliAvailable && resolved.effective.source === "anon") {
16746
+ messages.push(
16747
+ ` ${name}: Register via CLI (${agent.name} mcp add)`
16748
+ );
16749
+ } else if (agent.mcpConfigPath !== null) {
16750
+ messages.push(
16751
+ ` ${name}: Write config to ${agent.mcpConfigPath}`
16752
+ );
16753
+ }
16754
+ if (agent.infoFilePath !== null) {
16755
+ messages.push(
16756
+ ` ${name}: Inject info section into ${agent.infoFilePath}`
16757
+ );
16758
+ }
16759
+ }
16760
+ messages.push(
16761
+ "",
16762
+ " Update .gitignore with MCP config paths",
16763
+ " Create .glasstrace/mcp-connected marker"
16749
16764
  );
16765
+ return { exitCode: 0, results: [], messages };
16750
16766
  }
16751
- try {
16752
- for (const configFile of MCP_CONFIG_FILES) {
16753
- const configPath = path5.join(projectRoot, configFile);
16754
- if (!fs5.existsSync(configPath)) {
16755
- continue;
16756
- }
16757
- const content = fs5.readFileSync(configPath, "utf-8");
16758
- const result = processJsonMcpConfig(content);
16759
- if (result.action === "deleted") {
16760
- if (!dryRun) {
16761
- fs5.unlinkSync(configPath);
16762
- }
16763
- summary.push(`${prefix}Deleted ${configFile}`);
16764
- } else if (result.action === "removed-key" && result.content !== void 0) {
16765
- if (!dryRun) {
16766
- fs5.writeFileSync(configPath, result.content, "utf-8");
16767
- }
16768
- summary.push(`${prefix}Removed glasstrace from ${configFile}`);
16769
- }
16770
- }
16771
- const codexConfigPath = path5.join(projectRoot, ".codex", "config.toml");
16772
- if (fs5.existsSync(codexConfigPath)) {
16773
- const content = fs5.readFileSync(codexConfigPath, "utf-8");
16774
- const tomlResult = processTomlMcpConfig(content);
16775
- if (tomlResult.action === "deleted") {
16776
- if (!dryRun) {
16777
- fs5.unlinkSync(codexConfigPath);
16778
- }
16779
- summary.push(`${prefix}Deleted .codex/config.toml`);
16780
- } else if (tomlResult.action === "removed-section" && tomlResult.content !== void 0) {
16781
- if (!dryRun) {
16782
- fs5.writeFileSync(codexConfigPath, tomlResult.content, "utf-8");
16767
+ const results = [];
16768
+ const bearer = resolved.effective.key;
16769
+ for (const agent of targetAgents) {
16770
+ const name = formatAgentName(agent.name);
16771
+ if (agent.name !== "generic") {
16772
+ const cliSuccess = await registerViaCli(agent, bearer);
16773
+ if (cliSuccess) {
16774
+ const infoContent = generateInfoSection(agent, MCP_ENDPOINT);
16775
+ if (infoContent !== "") {
16776
+ await injectInfoSection(agent, infoContent, projectRoot);
16783
16777
  }
16784
- summary.push(`${prefix}Removed glasstrace from .codex/config.toml`);
16778
+ results.push({
16779
+ agent: agent.name,
16780
+ success: true,
16781
+ method: "cli",
16782
+ message: `${name}: Registered via CLI`
16783
+ });
16784
+ continue;
16785
16785
  }
16786
16786
  }
16787
- const hasWindsurfMarkers = fs5.existsSync(path5.join(projectRoot, ".windsurfrules")) || fs5.existsSync(path5.join(projectRoot, ".windsurf"));
16788
- if (hasWindsurfMarkers) {
16789
- const windsurfConfigPath = path5.join(
16790
- os.homedir(),
16791
- ".codeium",
16792
- "windsurf",
16793
- "mcp_config.json"
16794
- );
16795
- if (fs5.existsSync(windsurfConfigPath)) {
16796
- const content = fs5.readFileSync(windsurfConfigPath, "utf-8");
16797
- const windsurfResult = processJsonMcpConfig(content);
16798
- const home = os.homedir();
16799
- const displayPath = windsurfConfigPath.startsWith(home) ? "~" + windsurfConfigPath.slice(home.length) : windsurfConfigPath;
16800
- if (windsurfResult.action === "deleted") {
16801
- if (!dryRun) {
16802
- fs5.unlinkSync(windsurfConfigPath);
16803
- }
16804
- summary.push(
16805
- `${prefix}Deleted global Windsurf config (${displayPath})`
16806
- );
16807
- } else if (windsurfResult.action === "removed-key" && windsurfResult.content !== void 0) {
16808
- if (!dryRun) {
16809
- fs5.writeFileSync(windsurfConfigPath, windsurfResult.content, "utf-8");
16787
+ if (agent.mcpConfigPath !== null) {
16788
+ try {
16789
+ const configContent = generateMcpConfig(agent, MCP_ENDPOINT, bearer);
16790
+ await writeMcpConfig(agent, configContent, projectRoot);
16791
+ if (fs6.existsSync(agent.mcpConfigPath)) {
16792
+ const infoContent = generateInfoSection(agent, MCP_ENDPOINT);
16793
+ if (infoContent !== "") {
16794
+ await injectInfoSection(agent, infoContent, projectRoot);
16810
16795
  }
16811
- summary.push(
16812
- `${prefix}Removed glasstrace from global Windsurf config (${displayPath})`
16813
- );
16796
+ results.push({
16797
+ agent: agent.name,
16798
+ success: true,
16799
+ method: "file",
16800
+ message: `${name}: Configured via ${agent.mcpConfigPath}`
16801
+ });
16802
+ continue;
16814
16803
  }
16804
+ results.push({
16805
+ agent: agent.name,
16806
+ success: false,
16807
+ method: "file",
16808
+ message: `${name}: Failed to write config to ${agent.mcpConfigPath} (permission denied)`
16809
+ });
16810
+ continue;
16811
+ } catch (err) {
16812
+ results.push({
16813
+ agent: agent.name,
16814
+ success: false,
16815
+ method: "file",
16816
+ message: `${name}: Failed - ${err instanceof Error ? err.message : String(err)}`
16817
+ });
16818
+ continue;
16815
16819
  }
16816
16820
  }
16817
- } catch (err) {
16818
- errors.push(
16819
- `Failed to process MCP config: ${err instanceof Error ? err.message : String(err)}`
16821
+ results.push({
16822
+ agent: agent.name,
16823
+ success: false,
16824
+ method: "skipped",
16825
+ message: `${name}: No registration method available`
16826
+ });
16827
+ }
16828
+ await updateGitignore(
16829
+ [".mcp.json", ".cursor/mcp.json", ".gemini/settings.json", ".codex/config.toml"],
16830
+ projectRoot
16831
+ );
16832
+ const anySuccess = results.some((r) => r.success);
16833
+ if (anySuccess) {
16834
+ await writeMcpMarker(projectRoot, {
16835
+ credentialSource: resolved.effective.source,
16836
+ credentialHash: identityFingerprint(resolved.effective.key)
16837
+ });
16838
+ }
16839
+ messages.push("", "MCP registration summary:");
16840
+ for (const result of results) {
16841
+ const icon = result.success ? "+" : "-";
16842
+ messages.push(` [${icon}] ${result.message}`);
16843
+ }
16844
+ if (results.length === 0) {
16845
+ messages.push(
16846
+ " No agents detected. Place agent marker files (e.g., CLAUDE.md, .cursor/) in your project."
16820
16847
  );
16821
16848
  }
16822
- try {
16823
- for (const infoFile of AGENT_INFO_FILES) {
16824
- const filePath = path5.join(projectRoot, infoFile);
16825
- if (!fs5.existsSync(filePath)) {
16826
- continue;
16827
- }
16828
- const content = fs5.readFileSync(filePath, "utf-8");
16829
- const result = removeMarkerSection(content);
16830
- if (result.removed) {
16831
- if (result.content.trim().length === 0) {
16832
- if (!dryRun) {
16833
- fs5.unlinkSync(filePath);
16834
- }
16835
- summary.push(`${prefix}Deleted ${infoFile} (only contained Glasstrace section)`);
16836
- } else {
16837
- if (!dryRun) {
16838
- fs5.writeFileSync(filePath, result.content, "utf-8");
16839
- }
16840
- summary.push(`${prefix}Removed Glasstrace section from ${infoFile}`);
16841
- }
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
+ }
16860
+ if (!anySuccess && results.length > 0) {
16861
+ messages.push(
16862
+ "",
16863
+ "All agent registrations failed. Check errors above."
16864
+ );
16865
+ return { exitCode: 1, results, messages };
16866
+ }
16867
+ if (anySuccess) {
16868
+ messages.push("", "MCP registration complete.");
16869
+ }
16870
+ return { exitCode: 0, results, messages };
16871
+ }
16872
+ var import_node_child_process2, fs6, path6, import_node_util, execFileAsync;
16873
+ var init_mcp_add = __esm({
16874
+ "src/cli/mcp-add.ts"() {
16875
+ "use strict";
16876
+ import_node_child_process2 = require("node:child_process");
16877
+ fs6 = __toESM(require("node:fs"), 1);
16878
+ path6 = __toESM(require("node:path"), 1);
16879
+ import_node_util = require("node:util");
16880
+ init_mcp_runtime();
16881
+ init_detect();
16882
+ init_configs();
16883
+ init_inject();
16884
+ init_constants();
16885
+ execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
16886
+ }
16887
+ });
16888
+
16889
+ // src/cli/validate.ts
16890
+ var validate_exports = {};
16891
+ __export(validate_exports, {
16892
+ hasGlasstraceImport: () => hasGlasstraceImport,
16893
+ hasRegisterGlasstraceImport: () => hasRegisterGlasstraceImport,
16894
+ runValidate: () => runValidate
16895
+ });
16896
+ function hasGlasstraceImport(content) {
16897
+ return /@glasstrace\/sdk/.test(content);
16898
+ }
16899
+ function hasRegisterGlasstraceImport(content) {
16900
+ const match = /import\s*\{([^}]+)\}\s*from\s*["']@glasstrace\/sdk["']/;
16901
+ const importMatch = match.exec(content);
16902
+ if (!importMatch) return false;
16903
+ return importMatch[1].split(",").map((s) => s.trim()).includes("registerGlasstrace");
16904
+ }
16905
+ async function runValidate(options) {
16906
+ const { projectRoot } = options;
16907
+ const issues = [];
16908
+ const glasstraceDir = path7.join(projectRoot, ".glasstrace");
16909
+ const instrumentationPath = path7.join(projectRoot, "instrumentation.ts");
16910
+ const markerPath = path7.join(glasstraceDir, "mcp-connected");
16911
+ const glasstraceDirExists = isDirectorySafe(glasstraceDir);
16912
+ const instrumentationExists = fs7.existsSync(instrumentationPath);
16913
+ const instrumentationContent = instrumentationExists ? safeReadFile(instrumentationPath) : null;
16914
+ const markerExists = fs7.existsSync(markerPath);
16915
+ const mcpConfigsPresent = MCP_CONFIG_CANDIDATES.filter(
16916
+ (rel) => fs7.existsSync(path7.join(projectRoot, rel))
16917
+ );
16918
+ if (glasstraceDirExists) {
16919
+ if (instrumentationContent === null || !hasRegisterGlasstraceImport(instrumentationContent)) {
16920
+ issues.push({
16921
+ code: "glasstrace-dir-without-register-import",
16922
+ message: ".glasstrace/ exists but instrumentation.ts is missing the registerGlasstrace import.",
16923
+ fix: "Run `npx glasstrace init` to re-scaffold instrumentation.ts, or remove .glasstrace/ if the SDK is no longer in use."
16924
+ });
16925
+ }
16926
+ }
16927
+ if (!glasstraceDirExists && instrumentationContent !== null) {
16928
+ if (hasGlasstraceImport(instrumentationContent)) {
16929
+ issues.push({
16930
+ code: "sdk-import-without-glasstrace-dir",
16931
+ message: "instrumentation.ts imports from @glasstrace/sdk but .glasstrace/ is missing.",
16932
+ fix: "Run `npx glasstrace init` to recreate .glasstrace/, or `npx glasstrace uninit` to fully remove the SDK."
16933
+ });
16934
+ }
16935
+ }
16936
+ if (markerExists && mcpConfigsPresent.length === 0) {
16937
+ issues.push({
16938
+ code: "mcp-marker-without-configs",
16939
+ message: ".glasstrace/mcp-connected marker is present but no MCP config files were found.",
16940
+ fix: "Run `npx glasstrace mcp add --force` to regenerate MCP configs, or delete .glasstrace/mcp-connected."
16941
+ });
16942
+ }
16943
+ if (!markerExists && mcpConfigsPresent.length > 0) {
16944
+ issues.push({
16945
+ code: "mcp-configs-without-marker",
16946
+ message: `MCP config files exist (${mcpConfigsPresent.join(", ")}) but .glasstrace/mcp-connected marker is missing.`,
16947
+ fix: "Run `npx glasstrace init` to re-register the marker, or `npx glasstrace uninit` to fully remove MCP configuration."
16948
+ });
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
+ });
16842
16962
  }
16963
+ } catch {
16843
16964
  }
16844
- } catch (err) {
16845
- errors.push(
16846
- `Failed to process agent info files: ${err instanceof Error ? err.message : String(err)}`
16965
+ }
16966
+ const summary = [];
16967
+ if (issues.length === 0) {
16968
+ summary.push("Glasstrace install state is consistent.");
16969
+ } else {
16970
+ summary.push(
16971
+ `Detected ${issues.length} inconsistenc${issues.length === 1 ? "y" : "ies"} in Glasstrace install state:`
16847
16972
  );
16848
16973
  }
16849
- if (summary.length === 0 && errors.length === 0) {
16850
- summary.push("No Glasstrace artifacts found \u2014 nothing to do.");
16974
+ return {
16975
+ exitCode: issues.length > 0 ? 1 : 0,
16976
+ summary,
16977
+ issues
16978
+ };
16979
+ }
16980
+ function safeReadFile(filePath) {
16981
+ try {
16982
+ return fs7.readFileSync(filePath, "utf-8");
16983
+ } catch {
16984
+ return null;
16851
16985
  }
16852
- return { exitCode: errors.length > 0 ? 1 : 0, summary, warnings, errors };
16853
16986
  }
16854
- var fs5, os, path5, MCP_CONFIG_FILES, AGENT_INFO_FILES;
16855
- var init_uninit = __esm({
16856
- "src/cli/uninit.ts"() {
16987
+ function isDirectorySafe(dirPath) {
16988
+ try {
16989
+ if (!fs7.existsSync(dirPath)) return false;
16990
+ return fs7.statSync(dirPath).isDirectory();
16991
+ } catch {
16992
+ return false;
16993
+ }
16994
+ }
16995
+ var fs7, path7, MCP_CONFIG_CANDIDATES;
16996
+ var init_validate = __esm({
16997
+ "src/cli/validate.ts"() {
16857
16998
  "use strict";
16858
- fs5 = __toESM(require("node:fs"), 1);
16859
- os = __toESM(require("node:os"), 1);
16860
- path5 = __toESM(require("node:path"), 1);
16861
- init_constants();
16862
- init_scaffolder();
16863
- init_discovery_file();
16864
- MCP_CONFIG_FILES = [".mcp.json", ".cursor/mcp.json", ".gemini/settings.json"];
16865
- AGENT_INFO_FILES = [
16866
- "CLAUDE.md",
16867
- "codex.md",
16868
- ".cursorrules"
16999
+ fs7 = __toESM(require("node:fs"), 1);
17000
+ path7 = __toESM(require("node:path"), 1);
17001
+ init_mcp_runtime();
17002
+ MCP_CONFIG_CANDIDATES = [
17003
+ ".mcp.json",
17004
+ ".cursor/mcp.json",
17005
+ ".gemini/settings.json",
17006
+ ".codex/config.toml",
17007
+ ".glasstrace/mcp.json"
16869
17008
  ];
16870
17009
  }
16871
17010
  });
16872
17011
 
16873
- // src/cli/mcp-add.ts
16874
- var mcp_add_exports = {};
16875
- __export(mcp_add_exports, {
16876
- mcpAdd: () => mcpAdd
17012
+ // src/cli/status.ts
17013
+ var status_exports = {};
17014
+ __export(status_exports, {
17015
+ runStatus: () => runStatus
16877
17016
  });
16878
- async function registerViaCli(agent, anonKey) {
16879
- if (!agent.cliAvailable) {
17017
+ function runStatus(options) {
17018
+ const root = options.projectRoot;
17019
+ return {
17020
+ installed: checkInstalled(root),
17021
+ initialized: checkInitialized(root),
17022
+ instrumentation: checkInstrumentation(root),
17023
+ configWrapped: checkConfigWrapped(root),
17024
+ anonKey: checkAnonKey(root),
17025
+ mcpConfigured: checkMcpConfigured(root),
17026
+ agents: checkAgents(root),
17027
+ runtime: readRuntimeState(root)
17028
+ };
17029
+ }
17030
+ function checkInstalled(root) {
17031
+ try {
17032
+ const pkgPath = path8.join(root, "package.json");
17033
+ const content = fs8.readFileSync(pkgPath, "utf-8");
17034
+ const pkg = JSON.parse(content);
17035
+ const deps = pkg["dependencies"];
17036
+ const devDeps = pkg["devDependencies"];
17037
+ return deps != null && "@glasstrace/sdk" in deps || devDeps != null && "@glasstrace/sdk" in devDeps;
17038
+ } catch {
16880
17039
  return false;
16881
17040
  }
17041
+ }
17042
+ function checkInitialized(root) {
16882
17043
  try {
16883
- switch (agent.name) {
16884
- case "claude": {
16885
- const payload = JSON.stringify({
16886
- type: "http",
16887
- url: MCP_ENDPOINT,
16888
- headers: { Authorization: `Bearer ${anonKey}` }
16889
- });
16890
- await execFileAsync("claude", [
16891
- "mcp",
16892
- "add-json",
16893
- "glasstrace",
16894
- payload,
16895
- "--scope",
16896
- "project"
16897
- ]);
16898
- return true;
16899
- }
16900
- case "codex": {
16901
- await execFileAsync("codex", [
16902
- "mcp",
16903
- "add",
16904
- "glasstrace",
16905
- "--url",
16906
- MCP_ENDPOINT
16907
- ]);
16908
- const configPath = agent.mcpConfigPath;
16909
- if (configPath !== null && fs6.existsSync(configPath)) {
16910
- const content = fs6.readFileSync(configPath, "utf-8");
16911
- if (!content.includes("bearer_token_env_var")) {
16912
- const appendContent = content.endsWith("\n") ? "" : "\n";
16913
- fs6.writeFileSync(
16914
- configPath,
16915
- content + appendContent + 'bearer_token_env_var = "GLASSTRACE_API_KEY"\n',
16916
- "utf-8"
16917
- );
16918
- }
16919
- }
16920
- process.stderr.write(
16921
- " Note: Set GLASSTRACE_API_KEY environment variable for Codex authentication.\n"
16922
- );
17044
+ return fs8.statSync(path8.join(root, ".glasstrace")).isDirectory();
17045
+ } catch {
17046
+ return false;
17047
+ }
17048
+ }
17049
+ function checkInstrumentation(root) {
17050
+ for (const name of INSTRUMENTATION_FILES) {
17051
+ try {
17052
+ const content = fs8.readFileSync(path8.join(root, name), "utf-8");
17053
+ if (content.includes("registerGlasstrace")) {
16923
17054
  return true;
16924
17055
  }
16925
- case "gemini": {
16926
- await execFileAsync("gemini", [
16927
- "mcp",
16928
- "add",
16929
- "--transport",
16930
- "http",
16931
- "--header",
16932
- `Authorization: Bearer ${anonKey}`,
16933
- "glasstrace",
16934
- MCP_ENDPOINT
16935
- ]);
17056
+ } catch {
17057
+ }
17058
+ }
17059
+ return false;
17060
+ }
17061
+ function checkConfigWrapped(root) {
17062
+ for (const name of NEXT_CONFIG_NAMES) {
17063
+ try {
17064
+ const content = fs8.readFileSync(path8.join(root, name), "utf-8");
17065
+ if (content.includes("withGlasstraceConfig")) {
16936
17066
  return true;
16937
17067
  }
16938
- default:
16939
- return false;
17068
+ } catch {
16940
17069
  }
17070
+ }
17071
+ return false;
17072
+ }
17073
+ function checkAnonKey(root) {
17074
+ try {
17075
+ return fs8.statSync(path8.join(root, ".glasstrace", "anon_key")).isFile();
16941
17076
  } catch {
16942
17077
  return false;
16943
17078
  }
16944
17079
  }
16945
- async function mcpAdd(options) {
16946
- const force = options?.force ?? false;
16947
- const dryRun = options?.dryRun ?? false;
16948
- const projectRoot = process.cwd();
16949
- const messages = [];
16950
- const anonKey = await readAnonKey(projectRoot);
16951
- if (anonKey === null) {
16952
- return {
16953
- exitCode: 1,
16954
- results: [],
16955
- messages: ["Error: Run `glasstrace init` first to generate an API key."]
16956
- };
16957
- }
16958
- const markerPath = path6.join(projectRoot, ".glasstrace", "mcp-connected");
16959
- if (fs6.existsSync(markerPath) && !force) {
16960
- return {
16961
- exitCode: 0,
16962
- results: [],
16963
- messages: ["MCP already configured. Use --force to reconfigure."]
16964
- };
16965
- }
16966
- const agents = await detectAgents(projectRoot);
16967
- const detectedNonGeneric = agents.filter((a) => a.name !== "generic");
16968
- const targetAgents = detectedNonGeneric.length > 0 ? detectedNonGeneric : agents.filter((a) => a.name === "generic");
16969
- if (dryRun) {
16970
- messages.push("Dry run: would perform the following actions:", "");
16971
- for (const agent of targetAgents) {
16972
- const name = formatAgentName(agent.name);
16973
- if (agent.cliAvailable) {
16974
- messages.push(
16975
- ` ${name}: Register via CLI (${agent.name} mcp add)`
16976
- );
16977
- } else if (agent.mcpConfigPath !== null) {
16978
- messages.push(
16979
- ` ${name}: Write config to ${agent.mcpConfigPath}`
16980
- );
17080
+ function checkMcpConfigured(root) {
17081
+ for (const name of MCP_JSON_FILES) {
17082
+ try {
17083
+ const content = fs8.readFileSync(path8.join(root, name), "utf-8");
17084
+ const parsed = JSON.parse(content);
17085
+ const mcpServers = parsed["mcpServers"];
17086
+ if (mcpServers && typeof mcpServers === "object" && "glasstrace" in mcpServers) {
17087
+ return true;
16981
17088
  }
16982
- if (agent.infoFilePath !== null) {
16983
- messages.push(
16984
- ` ${name}: Inject info section into ${agent.infoFilePath}`
16985
- );
17089
+ } catch {
17090
+ }
17091
+ }
17092
+ for (const name of MCP_TOML_FILES) {
17093
+ try {
17094
+ const content = fs8.readFileSync(path8.join(root, name), "utf-8");
17095
+ if (content.includes("[mcp_servers.glasstrace]")) {
17096
+ return true;
16986
17097
  }
17098
+ } catch {
16987
17099
  }
16988
- messages.push(
16989
- "",
16990
- " Update .gitignore with MCP config paths",
16991
- " Create .glasstrace/mcp-connected marker"
16992
- );
16993
- return { exitCode: 0, results: [], messages };
16994
17100
  }
16995
- const results = [];
16996
- for (const agent of targetAgents) {
16997
- const name = formatAgentName(agent.name);
16998
- if (agent.name !== "generic") {
16999
- const cliSuccess = await registerViaCli(agent, anonKey);
17000
- if (cliSuccess) {
17001
- const infoContent = generateInfoSection(agent, MCP_ENDPOINT);
17002
- if (infoContent !== "") {
17003
- await injectInfoSection(agent, infoContent, projectRoot);
17004
- }
17005
- results.push({
17006
- agent: agent.name,
17007
- success: true,
17008
- method: "cli",
17009
- message: `${name}: Registered via CLI`
17010
- });
17011
- continue;
17101
+ return false;
17102
+ }
17103
+ function checkAgents(root) {
17104
+ const found = [];
17105
+ for (const name of AGENT_INFO_FILES2) {
17106
+ try {
17107
+ const content = fs8.readFileSync(path8.join(root, name), "utf-8");
17108
+ const hasHtmlMarkers = content.includes("<!-- glasstrace:mcp:start -->") && content.includes("<!-- glasstrace:mcp:end -->");
17109
+ const hasHashMarkers = content.includes("# glasstrace:mcp:start") && content.includes("# glasstrace:mcp:end");
17110
+ if (hasHtmlMarkers || hasHashMarkers) {
17111
+ found.push(name);
17012
17112
  }
17113
+ } catch {
17013
17114
  }
17014
- if (agent.mcpConfigPath !== null) {
17015
- try {
17016
- const configContent = generateMcpConfig(agent, MCP_ENDPOINT, anonKey);
17017
- await writeMcpConfig(agent, configContent, projectRoot);
17018
- if (fs6.existsSync(agent.mcpConfigPath)) {
17019
- const infoContent = generateInfoSection(agent, MCP_ENDPOINT);
17020
- if (infoContent !== "") {
17021
- await injectInfoSection(agent, infoContent, projectRoot);
17115
+ }
17116
+ return found;
17117
+ }
17118
+ function readRuntimeState(root) {
17119
+ const empty = {
17120
+ available: false,
17121
+ stale: false,
17122
+ coreState: null,
17123
+ authState: null,
17124
+ otelState: null,
17125
+ otelScenario: null,
17126
+ updatedAt: null,
17127
+ pid: null
17128
+ };
17129
+ try {
17130
+ const filePath = path8.join(root, ".glasstrace", "runtime-state.json");
17131
+ const content = fs8.readFileSync(filePath, "utf-8");
17132
+ const parsed = JSON.parse(content);
17133
+ const updatedAt = typeof parsed.updatedAt === "string" ? parsed.updatedAt : null;
17134
+ const pid = typeof parsed.pid === "number" ? parsed.pid : null;
17135
+ const core = parsed.core;
17136
+ const auth = parsed.auth;
17137
+ const otel = parsed.otel;
17138
+ const coreState = typeof core?.state === "string" ? core.state : null;
17139
+ const authState = typeof auth?.state === "string" ? auth.state : null;
17140
+ const otelState = typeof otel?.state === "string" ? otel.state : null;
17141
+ const otelScenario = typeof otel?.scenario === "string" ? otel.scenario : null;
17142
+ let stale = false;
17143
+ if (coreState === "SHUTDOWN") {
17144
+ stale = false;
17145
+ } else if (updatedAt) {
17146
+ const updatedMs = new Date(updatedAt).getTime();
17147
+ const age = Number.isFinite(updatedMs) ? Date.now() - updatedMs : Infinity;
17148
+ if (age > STALE_THRESHOLD_MS) {
17149
+ if (pid && pid > 0) {
17150
+ try {
17151
+ process.kill(pid, 0);
17152
+ stale = false;
17153
+ } catch (err) {
17154
+ const code = err?.code;
17155
+ if (code === "EPERM") {
17156
+ stale = false;
17157
+ } else {
17158
+ stale = true;
17159
+ }
17022
17160
  }
17023
- results.push({
17024
- agent: agent.name,
17025
- success: true,
17026
- method: "file",
17027
- message: `${name}: Configured via ${agent.mcpConfigPath}`
17028
- });
17029
- continue;
17161
+ } else {
17162
+ stale = true;
17030
17163
  }
17031
- results.push({
17032
- agent: agent.name,
17033
- success: false,
17034
- method: "file",
17035
- message: `${name}: Failed to write config to ${agent.mcpConfigPath} (permission denied)`
17036
- });
17037
- continue;
17038
- } catch (err) {
17039
- results.push({
17040
- agent: agent.name,
17041
- success: false,
17042
- method: "file",
17043
- message: `${name}: Failed - ${err instanceof Error ? err.message : String(err)}`
17044
- });
17045
- continue;
17046
17164
  }
17047
17165
  }
17048
- results.push({
17049
- agent: agent.name,
17050
- success: false,
17051
- method: "skipped",
17052
- message: `${name}: No registration method available`
17053
- });
17054
- }
17055
- await updateGitignore(
17056
- [".mcp.json", ".cursor/mcp.json", ".gemini/settings.json", ".codex/config.toml"],
17057
- projectRoot
17058
- );
17059
- const anySuccess = results.some((r) => r.success);
17060
- if (anySuccess) {
17061
- await scaffoldMcpMarker(projectRoot, anonKey);
17062
- }
17063
- messages.push("", "MCP registration summary:");
17064
- for (const result of results) {
17065
- const icon = result.success ? "+" : "-";
17066
- messages.push(` [${icon}] ${result.message}`);
17067
- }
17068
- if (results.length === 0) {
17069
- messages.push(
17070
- " No agents detected. Place agent marker files (e.g., CLAUDE.md, .cursor/) in your project."
17071
- );
17072
- }
17073
- if (!anySuccess && results.length > 0) {
17074
- messages.push(
17075
- "",
17076
- "All agent registrations failed. Check errors above."
17077
- );
17078
- return { exitCode: 1, results, messages };
17079
- }
17080
- if (anySuccess) {
17081
- messages.push("", "MCP registration complete.");
17166
+ return {
17167
+ available: true,
17168
+ stale,
17169
+ coreState,
17170
+ authState,
17171
+ otelState,
17172
+ otelScenario,
17173
+ updatedAt,
17174
+ pid
17175
+ };
17176
+ } catch {
17177
+ return empty;
17082
17178
  }
17083
- return { exitCode: 0, results, messages };
17084
17179
  }
17085
- var import_node_child_process2, fs6, path6, import_node_util, execFileAsync;
17086
- var init_mcp_add = __esm({
17087
- "src/cli/mcp-add.ts"() {
17180
+ var fs8, path8, MCP_JSON_FILES, MCP_TOML_FILES, AGENT_INFO_FILES2, INSTRUMENTATION_FILES, STALE_THRESHOLD_MS;
17181
+ var init_status = __esm({
17182
+ "src/cli/status.ts"() {
17088
17183
  "use strict";
17089
- import_node_child_process2 = require("node:child_process");
17090
- fs6 = __toESM(require("node:fs"), 1);
17091
- path6 = __toESM(require("node:path"), 1);
17092
- import_node_util = require("node:util");
17093
- init_anon_key();
17094
- init_detect();
17095
- init_configs();
17096
- init_inject();
17097
- init_scaffolder();
17184
+ fs8 = __toESM(require("node:fs"), 1);
17185
+ path8 = __toESM(require("node:path"), 1);
17098
17186
  init_constants();
17099
- execFileAsync = (0, import_node_util.promisify)(import_node_child_process2.execFile);
17187
+ MCP_JSON_FILES = [".mcp.json", ".cursor/mcp.json", ".gemini/settings.json", ".glasstrace/mcp.json"];
17188
+ MCP_TOML_FILES = [".codex/config.toml"];
17189
+ AGENT_INFO_FILES2 = [
17190
+ "CLAUDE.md",
17191
+ "codex.md",
17192
+ ".cursorrules"
17193
+ ];
17194
+ INSTRUMENTATION_FILES = [
17195
+ "instrumentation.ts",
17196
+ "instrumentation.js",
17197
+ "instrumentation.mjs",
17198
+ "src/instrumentation.ts",
17199
+ "src/instrumentation.js",
17200
+ "src/instrumentation.mjs"
17201
+ ];
17202
+ STALE_THRESHOLD_MS = 3e4;
17100
17203
  }
17101
17204
  });
17102
17205
 
17103
- // src/cli/validate.ts
17104
- var validate_exports = {};
17105
- __export(validate_exports, {
17106
- hasGlasstraceImport: () => hasGlasstraceImport,
17107
- hasRegisterGlasstraceImport: () => hasRegisterGlasstraceImport,
17108
- runValidate: () => runValidate
17206
+ // src/cli/init.ts
17207
+ var init_exports = {};
17208
+ __export(init_exports, {
17209
+ decideMcpConfigAction: () => decideMcpConfigAction,
17210
+ gitignoreExcludesDiscoveryFile: () => gitignoreExcludesDiscoveryFile,
17211
+ meetsNodeVersion: () => meetsNodeVersion,
17212
+ rollbackSteps: () => rollbackSteps,
17213
+ runInit: () => runInit,
17214
+ verifyAnonKeyRegistration: () => verifyAnonKeyRegistration
17109
17215
  });
17110
- function hasGlasstraceImport(content) {
17111
- return /@glasstrace\/sdk/.test(content);
17112
- }
17113
- function hasRegisterGlasstraceImport(content) {
17114
- const match = /import\s*\{([^}]+)\}\s*from\s*["']@glasstrace\/sdk["']/;
17115
- const importMatch = match.exec(content);
17116
- if (!importMatch) return false;
17117
- return importMatch[1].split(",").map((s) => s.trim()).includes("registerGlasstrace");
17216
+ module.exports = __toCommonJS(init_exports);
17217
+ var fs9 = __toESM(require("node:fs"), 1);
17218
+ var path9 = __toESM(require("node:path"), 1);
17219
+ var readline = __toESM(require("node:readline"), 1);
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
+ });
17118
17230
  }
17119
- function runValidate(options) {
17120
- const { projectRoot } = options;
17121
- const issues = [];
17122
- const glasstraceDir = path7.join(projectRoot, ".glasstrace");
17123
- const instrumentationPath = path7.join(projectRoot, "instrumentation.ts");
17124
- const markerPath = path7.join(glasstraceDir, "mcp-connected");
17125
- const glasstraceDirExists = isDirectorySafe(glasstraceDir);
17126
- const instrumentationExists = fs7.existsSync(instrumentationPath);
17127
- const instrumentationContent = instrumentationExists ? safeReadFile(instrumentationPath) : null;
17128
- const markerExists = fs7.existsSync(markerPath);
17129
- const mcpConfigsPresent = MCP_CONFIG_CANDIDATES.filter(
17130
- (rel) => fs7.existsSync(path7.join(projectRoot, rel))
17131
- );
17132
- if (glasstraceDirExists) {
17133
- if (instrumentationContent === null || !hasRegisterGlasstraceImport(instrumentationContent)) {
17134
- issues.push({
17135
- code: "glasstrace-dir-without-register-import",
17136
- message: ".glasstrace/ exists but instrumentation.ts is missing the registerGlasstrace import.",
17137
- fix: "Run `npx glasstrace init` to re-scaffold instrumentation.ts, or remove .glasstrace/ if the SDK is no longer in use."
17138
- });
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
+ }
17139
17277
  }
17278
+ } else {
17279
+ modified = importLine + content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);
17140
17280
  }
17141
- if (!glasstraceDirExists && instrumentationContent !== null) {
17142
- if (hasGlasstraceImport(instrumentationContent)) {
17143
- issues.push({
17144
- code: "sdk-import-without-glasstrace-dir",
17145
- message: "instrumentation.ts imports from @glasstrace/sdk but .glasstrace/ is missing.",
17146
- fix: "Run `npx glasstrace init` to recreate .glasstrace/, or `npx glasstrace uninit` to fully remove the SDK."
17147
- });
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);
17148
17299
  }
17149
17300
  }
17150
- if (markerExists && mcpConfigsPresent.length === 0) {
17151
- issues.push({
17152
- code: "mcp-marker-without-configs",
17153
- message: ".glasstrace/mcp-connected marker is present but no MCP config files were found.",
17154
- fix: "Run `npx glasstrace mcp add --force` to regenerate MCP configs, or delete .glasstrace/mcp-connected."
17155
- });
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
+ };
17156
17311
  }
17157
- if (!markerExists && mcpConfigsPresent.length > 0) {
17158
- issues.push({
17159
- code: "mcp-configs-without-marker",
17160
- message: `MCP config files exist (${mcpConfigsPresent.join(", ")}) but .glasstrace/mcp-connected marker is missing.`,
17161
- fix: "Run `npx glasstrace init` to re-register the marker, or `npx glasstrace uninit` to fully remove MCP configuration."
17162
- });
17312
+ if (srcExisting.length > 0) {
17313
+ return {
17314
+ target: srcExisting[0],
17315
+ layout: "src",
17316
+ existing,
17317
+ rootExisting,
17318
+ srcExisting,
17319
+ conflict: false
17320
+ };
17163
17321
  }
17164
- const summary = [];
17165
- if (issues.length === 0) {
17166
- summary.push("Glasstrace install state is consistent.");
17167
- } else {
17168
- summary.push(
17169
- `Detected ${issues.length} inconsistenc${issues.length === 1 ? "y" : "ies"} in Glasstrace install state:`
17170
- );
17322
+ if (rootExisting.length > 0) {
17323
+ return {
17324
+ target: rootExisting[0],
17325
+ layout: "root",
17326
+ existing,
17327
+ rootExisting,
17328
+ srcExisting,
17329
+ conflict: false
17330
+ };
17171
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");
17172
17335
  return {
17173
- exitCode: issues.length > 0 ? 1 : 0,
17174
- summary,
17175
- issues
17336
+ target,
17337
+ layout,
17338
+ existing,
17339
+ rootExisting,
17340
+ srcExisting,
17341
+ conflict: false
17176
17342
  };
17177
17343
  }
17178
- function safeReadFile(filePath) {
17344
+ function isDirectory(p) {
17179
17345
  try {
17180
- return fs7.readFileSync(filePath, "utf-8");
17346
+ return fs.statSync(p).isDirectory();
17181
17347
  } catch {
17182
- return null;
17348
+ return false;
17183
17349
  }
17184
17350
  }
17185
- function isDirectorySafe(dirPath) {
17351
+ function isRegularFile(p) {
17186
17352
  try {
17187
- if (!fs7.existsSync(dirPath)) return false;
17188
- return fs7.statSync(dirPath).isDirectory();
17353
+ const stat2 = fs.lstatSync(p);
17354
+ if (stat2.isSymbolicLink()) {
17355
+ return fs.statSync(p).isFile();
17356
+ }
17357
+ return stat2.isFile();
17189
17358
  } catch {
17190
17359
  return false;
17191
17360
  }
17192
17361
  }
17193
- var fs7, path7, MCP_CONFIG_CANDIDATES;
17194
- var init_validate = __esm({
17195
- "src/cli/validate.ts"() {
17196
- "use strict";
17197
- fs7 = __toESM(require("node:fs"), 1);
17198
- path7 = __toESM(require("node:path"), 1);
17199
- MCP_CONFIG_CANDIDATES = [
17200
- ".mcp.json",
17201
- ".cursor/mcp.json",
17202
- ".gemini/settings.json",
17203
- ".codex/config.toml"
17204
- ];
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
+ }
17205
17384
  }
17206
- });
17207
-
17208
- // src/cli/status.ts
17209
- var status_exports = {};
17210
- __export(status_exports, {
17211
- runStatus: () => runStatus
17212
- });
17213
- function runStatus(options) {
17214
- const root = options.projectRoot;
17215
- return {
17216
- installed: checkInstalled(root),
17217
- initialized: checkInitialized(root),
17218
- instrumentation: checkInstrumentation(root),
17219
- configWrapped: checkConfigWrapped(root),
17220
- anonKey: checkAnonKey(root),
17221
- mcpConfigured: checkMcpConfigured(root),
17222
- agents: checkAgents(root),
17223
- runtime: readRuntimeState(root)
17224
- };
17385
+ const trailingNewline = withImport.endsWith("\n") ? "" : "\n";
17386
+ return withImport + trailingNewline + functionBlock;
17225
17387
  }
17226
- function checkInstalled(root) {
17227
- try {
17228
- const pkgPath = path8.join(root, "package.json");
17229
- const content = fs8.readFileSync(pkgPath, "utf-8");
17230
- const pkg = JSON.parse(content);
17231
- const deps = pkg["dependencies"];
17232
- const devDeps = pkg["devDependencies"];
17233
- return deps != null && "@glasstrace/sdk" in deps || devDeps != null && "@glasstrace/sdk" in devDeps;
17234
- } catch {
17235
- return false;
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" };
17236
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();
17237
17435
  }
17238
- function checkInitialized(root) {
17239
- try {
17240
- return fs8.statSync(path8.join(root, ".glasstrace")).isDirectory();
17241
- } catch {
17242
- return false;
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 };
17243
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 };
17244
17462
  }
17245
- function checkInstrumentation(root) {
17246
- for (const name of INSTRUMENTATION_FILES) {
17247
- try {
17248
- const content = fs8.readFileSync(path8.join(root, name), "utf-8");
17249
- if (content.includes("registerGlasstrace")) {
17250
- return true;
17251
- }
17252
- } catch {
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;
17253
17472
  }
17254
17473
  }
17255
- return false;
17256
- }
17257
- function checkConfigWrapped(root) {
17258
- for (const name of NEXT_CONFIG_NAMES) {
17259
- try {
17260
- const content = fs8.readFileSync(path8.join(root, name), "utf-8");
17261
- if (content.includes("withGlasstraceConfig")) {
17262
- return true;
17263
- }
17264
- } catch {
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" };
17265
17490
  }
17491
+ const modified2 = importLine + "\n" + wrapResult2.content;
17492
+ fs.writeFileSync(configPath, modified2, "utf-8");
17493
+ return { modified: true };
17266
17494
  }
17267
- return false;
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 };
17268
17503
  }
17269
- function checkAnonKey(root) {
17270
- try {
17271
- return fs8.statSync(path8.join(root, ".glasstrace", "anon_key")).isFile();
17272
- } catch {
17273
- return false;
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 };
17274
17515
  }
17516
+ return {
17517
+ content: preamble + `export default withGlasstraceConfig(${expr});
17518
+ `,
17519
+ wrapped: true
17520
+ };
17275
17521
  }
17276
- function checkMcpConfigured(root) {
17277
- for (const name of MCP_JSON_FILES) {
17278
- try {
17279
- const content = fs8.readFileSync(path8.join(root, name), "utf-8");
17280
- const parsed = JSON.parse(content);
17281
- const mcpServers = parsed["mcpServers"];
17282
- if (mcpServers && typeof mcpServers === "object" && "glasstrace" in mcpServers) {
17283
- return true;
17284
- }
17285
- } catch {
17286
- }
17522
+ function wrapCJSExport(content) {
17523
+ const cjsMarker = "module.exports";
17524
+ const cjsIdx = content.lastIndexOf(cjsMarker);
17525
+ if (cjsIdx === -1) {
17526
+ return { content, wrapped: false };
17287
17527
  }
17288
- for (const name of MCP_TOML_FILES) {
17289
- try {
17290
- const content = fs8.readFileSync(path8.join(root, name), "utf-8");
17291
- if (content.includes("[mcp_servers.glasstrace]")) {
17292
- return true;
17293
- }
17294
- } catch {
17295
- }
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 };
17296
17533
  }
17297
- return false;
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
+ };
17298
17544
  }
17299
- function checkAgents(root) {
17300
- const found = [];
17301
- for (const name of AGENT_INFO_FILES2) {
17302
- try {
17303
- const content = fs8.readFileSync(path8.join(root, name), "utf-8");
17304
- const hasHtmlMarkers = content.includes("<!-- glasstrace:mcp:start -->") && content.includes("<!-- glasstrace:mcp:end -->");
17305
- const hasHashMarkers = content.includes("# glasstrace:mcp:start") && content.includes("# glasstrace:mcp:end");
17306
- if (hasHtmlMarkers || hasHashMarkers) {
17307
- found.push(name);
17308
- }
17309
- } catch {
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;
17310
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;
17311
17555
  }
17312
- return found;
17556
+ fs.writeFileSync(filePath, "# GLASSTRACE_API_KEY=your_key_here\n", "utf-8");
17557
+ return true;
17313
17558
  }
17314
- function readRuntimeState(root) {
17315
- const empty = {
17316
- available: false,
17317
- stale: false,
17318
- coreState: null,
17319
- authState: null,
17320
- otelState: null,
17321
- otelScenario: null,
17322
- updatedAt: null,
17323
- pid: null
17324
- };
17325
- try {
17326
- const filePath = path8.join(root, ".glasstrace", "runtime-state.json");
17327
- const content = fs8.readFileSync(filePath, "utf-8");
17328
- const parsed = JSON.parse(content);
17329
- const updatedAt = typeof parsed.updatedAt === "string" ? parsed.updatedAt : null;
17330
- const pid = typeof parsed.pid === "number" ? parsed.pid : null;
17331
- const core = parsed.core;
17332
- const auth = parsed.auth;
17333
- const otel = parsed.otel;
17334
- const coreState = typeof core?.state === "string" ? core.state : null;
17335
- const authState = typeof auth?.state === "string" ? auth.state : null;
17336
- const otelState = typeof otel?.state === "string" ? otel.state : null;
17337
- const otelScenario = typeof otel?.scenario === "string" ? otel.scenario : null;
17338
- let stale = false;
17339
- if (coreState === "SHUTDOWN") {
17340
- stale = false;
17341
- } else if (updatedAt) {
17342
- const updatedMs = new Date(updatedAt).getTime();
17343
- const age = Number.isFinite(updatedMs) ? Date.now() - updatedMs : Infinity;
17344
- if (age > STALE_THRESHOLD_MS) {
17345
- if (pid && pid > 0) {
17346
- try {
17347
- process.kill(pid, 0);
17348
- stale = false;
17349
- } catch (err) {
17350
- const code = err?.code;
17351
- if (code === "EPERM") {
17352
- stale = false;
17353
- } else {
17354
- stale = true;
17355
- }
17356
- }
17357
- } else {
17358
- stale = true;
17359
- }
17360
- }
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;
17361
17572
  }
17362
- return {
17363
- available: true,
17364
- stale,
17365
- coreState,
17366
- authState,
17367
- otelState,
17368
- otelScenario,
17369
- updatedAt,
17370
- pid
17371
- };
17372
- } catch {
17373
- return empty;
17573
+ const updated = existing.replace(keyRegex, `${keyMatch[1]}true`);
17574
+ fs.writeFileSync(filePath, updated, "utf-8");
17575
+ return true;
17374
17576
  }
17577
+ const separator = existing.endsWith("\n") ? "" : "\n";
17578
+ fs.writeFileSync(filePath, existing + separator + "GLASSTRACE_COVERAGE_MAP=true\n", "utf-8");
17579
+ return true;
17375
17580
  }
17376
- var fs8, path8, MCP_JSON_FILES, MCP_TOML_FILES, AGENT_INFO_FILES2, INSTRUMENTATION_FILES, STALE_THRESHOLD_MS;
17377
- var init_status = __esm({
17378
- "src/cli/status.ts"() {
17379
- "use strict";
17380
- fs8 = __toESM(require("node:fs"), 1);
17381
- path8 = __toESM(require("node:path"), 1);
17382
- init_constants();
17383
- MCP_JSON_FILES = [".mcp.json", ".cursor/mcp.json", ".gemini/settings.json", ".glasstrace/mcp.json"];
17384
- MCP_TOML_FILES = [".codex/config.toml"];
17385
- AGENT_INFO_FILES2 = [
17386
- "CLAUDE.md",
17387
- "codex.md",
17388
- ".cursorrules"
17389
- ];
17390
- INSTRUMENTATION_FILES = [
17391
- "instrumentation.ts",
17392
- "instrumentation.js",
17393
- "instrumentation.mjs",
17394
- "src/instrumentation.ts",
17395
- "src/instrumentation.js",
17396
- "src/instrumentation.mjs"
17397
- ];
17398
- STALE_THRESHOLD_MS = 3e4;
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;
17399
17592
  }
17400
- });
17593
+ fs.writeFileSync(filePath, ".glasstrace/\n", "utf-8");
17594
+ return true;
17595
+ }
17401
17596
 
17402
17597
  // src/cli/init.ts
17403
- var init_exports = {};
17404
- __export(init_exports, {
17405
- decideMcpConfigAction: () => decideMcpConfigAction,
17406
- gitignoreExcludesDiscoveryFile: () => gitignoreExcludesDiscoveryFile,
17407
- meetsNodeVersion: () => meetsNodeVersion,
17408
- rollbackSteps: () => rollbackSteps,
17409
- runInit: () => runInit,
17410
- verifyAnonKeyRegistration: () => verifyAnonKeyRegistration
17411
- });
17412
- module.exports = __toCommonJS(init_exports);
17413
- var fs9 = __toESM(require("node:fs"), 1);
17414
- var path9 = __toESM(require("node:path"), 1);
17415
- var readline = __toESM(require("node:readline"), 1);
17416
- init_scaffolder();
17598
+ init_mcp_runtime();
17417
17599
 
17418
17600
  // src/import-graph.ts
17419
17601
  var fs2 = __toESM(require("node:fs/promises"), 1);
@@ -18332,6 +18514,9 @@ Then add this as the first statement in your register() function:
18332
18514
  );
18333
18515
  }
18334
18516
  let anyConfigWritten = false;
18517
+ let anyConfigRewrittenWithBearer = false;
18518
+ const resolved = await resolveEffectiveMcpCredential(projectRoot);
18519
+ const bearer = resolved.effective?.key ?? anonKey;
18335
18520
  if (isCI) {
18336
18521
  const genericAgent = {
18337
18522
  name: "generic",
@@ -18340,7 +18525,7 @@ Then add this as the first statement in your register() function:
18340
18525
  cliAvailable: false,
18341
18526
  registrationCommand: null
18342
18527
  };
18343
- const genericConfig = generateMcpConfig(genericAgent, MCP_ENDPOINT, anonKey);
18528
+ const genericConfig = generateMcpConfig(genericAgent, MCP_ENDPOINT, bearer);
18344
18529
  const decision = await decideMcpConfigAction({
18345
18530
  configPath: genericAgent.mcpConfigPath,
18346
18531
  expectedContent: genericConfig,
@@ -18348,6 +18533,9 @@ Then add this as the first statement in your register() function:
18348
18533
  });
18349
18534
  if (decision !== "skip") {
18350
18535
  await writeMcpConfig(genericAgent, genericConfig, projectRoot);
18536
+ if (genericAgent.mcpConfigPath !== null && fs9.existsSync(genericAgent.mcpConfigPath)) {
18537
+ anyConfigRewrittenWithBearer = true;
18538
+ }
18351
18539
  }
18352
18540
  if (genericAgent.mcpConfigPath !== null && fs9.existsSync(genericAgent.mcpConfigPath)) {
18353
18541
  anyConfigWritten = true;
@@ -18368,17 +18556,18 @@ Then add this as the first statement in your register() function:
18368
18556
  cliAvailable: false,
18369
18557
  registrationCommand: null
18370
18558
  };
18371
- const genericConfig = generateMcpConfig(genericAgent, MCP_ENDPOINT, anonKey);
18559
+ const genericConfig = generateMcpConfig(genericAgent, MCP_ENDPOINT, bearer);
18372
18560
  await writeMcpConfig(genericAgent, genericConfig, projectRoot);
18373
18561
  if (genericAgent.mcpConfigPath !== null && fs9.existsSync(genericAgent.mcpConfigPath)) {
18374
18562
  anyConfigWritten = true;
18563
+ anyConfigRewrittenWithBearer = true;
18375
18564
  }
18376
18565
  agents = [];
18377
18566
  }
18378
18567
  const configuredNames = [];
18379
18568
  for (const agent of agents) {
18380
18569
  try {
18381
- const configContent = generateMcpConfig(agent, MCP_ENDPOINT, anonKey);
18570
+ const configContent = generateMcpConfig(agent, MCP_ENDPOINT, bearer);
18382
18571
  const decision = await decideMcpConfigAction({
18383
18572
  configPath: agent.mcpConfigPath,
18384
18573
  expectedContent: configContent,
@@ -18399,6 +18588,7 @@ Then add this as the first statement in your register() function:
18399
18588
  continue;
18400
18589
  }
18401
18590
  anyConfigWritten = true;
18591
+ anyConfigRewrittenWithBearer = true;
18402
18592
  const infoContent = generateInfoSection(agent, MCP_ENDPOINT);
18403
18593
  if (infoContent !== "") {
18404
18594
  await injectInfoSection(agent, infoContent, projectRoot);
@@ -18422,8 +18612,13 @@ Then add this as the first statement in your register() function:
18422
18612
  [".mcp.json", ".cursor/mcp.json", ".gemini/settings.json", ".codex/config.toml"],
18423
18613
  projectRoot
18424
18614
  );
18425
- if (anyConfigWritten) {
18426
- const markerCreated = await scaffoldMcpMarker(projectRoot, anonKey);
18615
+ if (anyConfigRewrittenWithBearer) {
18616
+ const markerSource = resolved.effective?.source ?? "anon";
18617
+ const markerHash = identityFingerprint(bearer);
18618
+ const markerCreated = await writeMcpMarker(projectRoot, {
18619
+ credentialSource: markerSource,
18620
+ credentialHash: markerHash
18621
+ });
18427
18622
  if (markerCreated) {
18428
18623
  summary.push("Created .glasstrace/mcp-connected marker");
18429
18624
  }
@@ -18492,7 +18687,7 @@ async function verifyAnonKeyRegistration(projectRoot) {
18492
18687
  }
18493
18688
  const baseConfig = resolveConfig({ apiKey: devKey });
18494
18689
  const config2 = { ...baseConfig, apiKey: devKey };
18495
- const sdkVersion = true ? "1.1.0" : "0.0.0-dev";
18690
+ const sdkVersion = true ? "1.1.2" : "0.0.0-dev";
18496
18691
  const result = await verifyInitReachable(config2, anonKey, sdkVersion);
18497
18692
  if (result.ok) {
18498
18693
  return { outcome: "verified" };