@glasstrace/sdk 0.7.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/init.cjs CHANGED
@@ -35,12 +35,68 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
35
35
  function identityFingerprint(token) {
36
36
  return `sha256:${(0, import_node_crypto.createHash)("sha256").update(token).digest("hex")}`;
37
37
  }
38
- async function scaffoldInstrumentation(projectRoot, force) {
39
- const filePath = path.join(projectRoot, "instrumentation.ts");
40
- if (fs.existsSync(filePath) && !force) {
41
- return false;
38
+ function hasRegisterGlasstraceCall(content) {
39
+ return content.split("\n").some((line) => {
40
+ const uncommented = line.replace(/\/\/.*$/, "");
41
+ return /\bregisterGlasstrace\s*\(/.test(uncommented);
42
+ });
43
+ }
44
+ function injectRegisterGlasstrace(content) {
45
+ if (hasRegisterGlasstraceCall(content)) {
46
+ return { injected: false, content };
47
+ }
48
+ const registerFnRegex = /export\s+(?:async\s+)?function\s+register\s*\([^)]*\)\s*\{/;
49
+ const match = registerFnRegex.exec(content);
50
+ if (!match) {
51
+ return { injected: false, content };
52
+ }
53
+ const afterBrace = content.slice(match.index + match[0].length);
54
+ const indentMatch = /\n([ \t]+)/.exec(afterBrace);
55
+ const indent = indentMatch ? indentMatch[1] : " ";
56
+ const importLine = 'import { registerGlasstrace } from "@glasstrace/sdk";\n';
57
+ const hasGlasstraceImport = content.includes("@glasstrace/sdk");
58
+ const insertPoint = match.index + match[0].length;
59
+ const callInjection = `
60
+ ${indent}// Glasstrace must be registered before other instrumentation
61
+ ${indent}registerGlasstrace();
62
+ `;
63
+ let modified;
64
+ if (hasGlasstraceImport) {
65
+ const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']@glasstrace\/sdk["']/;
66
+ const importMatch = importRegex.exec(content);
67
+ if (importMatch) {
68
+ const specifiers = importMatch[1];
69
+ const alreadyImported = specifiers.split(",").some((s) => s.trim() === "registerGlasstrace");
70
+ if (alreadyImported) {
71
+ modified = content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);
72
+ } else {
73
+ const existingImports = specifiers.trimEnd();
74
+ const separator = existingImports.endsWith(",") ? " " : ", ";
75
+ const updatedImport = `import {${existingImports}${separator}registerGlasstrace} from "@glasstrace/sdk"`;
76
+ modified = content.replace(importMatch[0], updatedImport);
77
+ const newMatch = registerFnRegex.exec(modified);
78
+ if (newMatch) {
79
+ const newInsertPoint = newMatch.index + newMatch[0].length;
80
+ modified = modified.slice(0, newInsertPoint) + callInjection + modified.slice(newInsertPoint);
81
+ }
82
+ }
83
+ } else {
84
+ modified = importLine + content;
85
+ const newMatch = registerFnRegex.exec(modified);
86
+ if (newMatch) {
87
+ const newInsertPoint = newMatch.index + newMatch[0].length;
88
+ modified = modified.slice(0, newInsertPoint) + callInjection + modified.slice(newInsertPoint);
89
+ }
90
+ }
91
+ } else {
92
+ modified = importLine + content.slice(0, insertPoint) + callInjection + content.slice(insertPoint);
42
93
  }
43
- const content = `import { registerGlasstrace } from "@glasstrace/sdk";
94
+ return { injected: true, content: modified };
95
+ }
96
+ async function scaffoldInstrumentation(projectRoot) {
97
+ const filePath = path.join(projectRoot, "instrumentation.ts");
98
+ if (!fs.existsSync(filePath)) {
99
+ const content = `import { registerGlasstrace } from "@glasstrace/sdk";
44
100
 
45
101
  export async function register() {
46
102
  // Glasstrace must be registered before Prisma instrumentation
@@ -49,8 +105,19 @@ export async function register() {
49
105
  registerGlasstrace();
50
106
  }
51
107
  `;
52
- fs.writeFileSync(filePath, content, "utf-8");
53
- return true;
108
+ fs.writeFileSync(filePath, content, "utf-8");
109
+ return { action: "created" };
110
+ }
111
+ const existing = fs.readFileSync(filePath, "utf-8");
112
+ if (hasRegisterGlasstraceCall(existing)) {
113
+ return { action: "already-registered" };
114
+ }
115
+ const result = injectRegisterGlasstrace(existing);
116
+ if (result.injected) {
117
+ fs.writeFileSync(filePath, result.content, "utf-8");
118
+ return { action: "injected" };
119
+ }
120
+ return { action: "unrecognized" };
54
121
  }
55
122
  async function scaffoldNextConfig(projectRoot) {
56
123
  let configPath;
@@ -472,10 +539,10 @@ function mergeDefs(...defs) {
472
539
  function cloneDef(schema) {
473
540
  return mergeDefs(schema._zod.def);
474
541
  }
475
- function getElementAtPath(obj, path5) {
476
- if (!path5)
542
+ function getElementAtPath(obj, path7) {
543
+ if (!path7)
477
544
  return obj;
478
- return path5.reduce((acc, key) => acc?.[key], obj);
545
+ return path7.reduce((acc, key) => acc?.[key], obj);
479
546
  }
480
547
  function promiseAllObject(promisesObj) {
481
548
  const keys = Object.keys(promisesObj);
@@ -787,11 +854,11 @@ function aborted(x, startIndex = 0) {
787
854
  }
788
855
  return false;
789
856
  }
790
- function prefixIssues(path5, issues) {
857
+ function prefixIssues(path7, issues) {
791
858
  return issues.map((iss) => {
792
859
  var _a2;
793
860
  (_a2 = iss).path ?? (_a2.path = []);
794
- iss.path.unshift(path5);
861
+ iss.path.unshift(path7);
795
862
  return iss;
796
863
  });
797
864
  }
@@ -1034,7 +1101,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
1034
1101
  }
1035
1102
  function treeifyError(error48, mapper = (issue2) => issue2.message) {
1036
1103
  const result = { errors: [] };
1037
- const processError = (error49, path5 = []) => {
1104
+ const processError = (error49, path7 = []) => {
1038
1105
  var _a2, _b;
1039
1106
  for (const issue2 of error49.issues) {
1040
1107
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -1044,7 +1111,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
1044
1111
  } else if (issue2.code === "invalid_element") {
1045
1112
  processError({ issues: issue2.issues }, issue2.path);
1046
1113
  } else {
1047
- const fullpath = [...path5, ...issue2.path];
1114
+ const fullpath = [...path7, ...issue2.path];
1048
1115
  if (fullpath.length === 0) {
1049
1116
  result.errors.push(mapper(issue2));
1050
1117
  continue;
@@ -1076,8 +1143,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
1076
1143
  }
1077
1144
  function toDotPath(_path) {
1078
1145
  const segs = [];
1079
- const path5 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1080
- for (const seg of path5) {
1146
+ const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1147
+ for (const seg of path7) {
1081
1148
  if (typeof seg === "number")
1082
1149
  segs.push(`[${seg}]`);
1083
1150
  else if (typeof seg === "symbol")
@@ -13841,13 +13908,13 @@ function resolveRef(ref, ctx) {
13841
13908
  if (!ref.startsWith("#")) {
13842
13909
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
13843
13910
  }
13844
- const path5 = ref.slice(1).split("/").filter(Boolean);
13845
- if (path5.length === 0) {
13911
+ const path7 = ref.slice(1).split("/").filter(Boolean);
13912
+ if (path7.length === 0) {
13846
13913
  return ctx.rootSchema;
13847
13914
  }
13848
13915
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
13849
- if (path5[0] === defsKey) {
13850
- const key = path5[1];
13916
+ if (path7[0] === defsKey) {
13917
+ const key = path7[1];
13851
13918
  if (!key || !ctx.defs[key]) {
13852
13919
  throw new Error(`Reference not found: ${ref}`);
13853
13920
  }
@@ -14824,9 +14891,9 @@ var init_anon_key = __esm({
14824
14891
  });
14825
14892
 
14826
14893
  // src/agent-detection/detect.ts
14827
- async function pathExists(path5, mode = import_node_fs.constants.R_OK) {
14894
+ async function pathExists(path7, mode = import_node_fs.constants.R_OK) {
14828
14895
  try {
14829
- await (0, import_promises2.access)(path5, mode);
14896
+ await (0, import_promises2.access)(path7, mode);
14830
14897
  return true;
14831
14898
  } catch {
14832
14899
  return false;
@@ -15380,11 +15447,11 @@ async function registerViaCli(agent, anonKey) {
15380
15447
  MCP_ENDPOINT
15381
15448
  ]);
15382
15449
  const configPath = agent.mcpConfigPath;
15383
- if (configPath !== null && fs3.existsSync(configPath)) {
15384
- const content = fs3.readFileSync(configPath, "utf-8");
15450
+ if (configPath !== null && fs4.existsSync(configPath)) {
15451
+ const content = fs4.readFileSync(configPath, "utf-8");
15385
15452
  if (!content.includes("bearer_token_env_var")) {
15386
15453
  const appendContent = content.endsWith("\n") ? "" : "\n";
15387
- fs3.writeFileSync(
15454
+ fs4.writeFileSync(
15388
15455
  configPath,
15389
15456
  content + appendContent + 'bearer_token_env_var = "GLASSTRACE_API_KEY"\n',
15390
15457
  "utf-8"
@@ -15429,8 +15496,8 @@ async function mcpAdd(options) {
15429
15496
  messages: ["Error: Run `glasstrace init` first to generate an API key."]
15430
15497
  };
15431
15498
  }
15432
- const markerPath = path3.join(projectRoot, ".glasstrace", "mcp-connected");
15433
- if (fs3.existsSync(markerPath) && !force) {
15499
+ const markerPath = path4.join(projectRoot, ".glasstrace", "mcp-connected");
15500
+ if (fs4.existsSync(markerPath) && !force) {
15434
15501
  return {
15435
15502
  exitCode: 0,
15436
15503
  results: [],
@@ -15489,7 +15556,7 @@ async function mcpAdd(options) {
15489
15556
  try {
15490
15557
  const configContent = generateMcpConfig(agent, MCP_ENDPOINT, anonKey);
15491
15558
  await writeMcpConfig(agent, configContent, projectRoot);
15492
- if (fs3.existsSync(agent.mcpConfigPath)) {
15559
+ if (fs4.existsSync(agent.mcpConfigPath)) {
15493
15560
  const infoContent = generateInfoSection(agent, MCP_ENDPOINT);
15494
15561
  if (infoContent !== "") {
15495
15562
  await injectInfoSection(agent, infoContent, projectRoot);
@@ -15556,13 +15623,13 @@ async function mcpAdd(options) {
15556
15623
  }
15557
15624
  return { exitCode: 0, results, messages };
15558
15625
  }
15559
- var import_node_child_process2, fs3, path3, import_node_util, execFileAsync;
15626
+ var import_node_child_process2, fs4, path4, import_node_util, execFileAsync;
15560
15627
  var init_mcp_add = __esm({
15561
15628
  "src/cli/mcp-add.ts"() {
15562
15629
  "use strict";
15563
15630
  import_node_child_process2 = require("child_process");
15564
- fs3 = __toESM(require("fs"), 1);
15565
- path3 = __toESM(require("path"), 1);
15631
+ fs4 = __toESM(require("fs"), 1);
15632
+ path4 = __toESM(require("path"), 1);
15566
15633
  import_node_util = require("util");
15567
15634
  init_anon_key();
15568
15635
  init_detect();
@@ -15574,6 +15641,552 @@ var init_mcp_add = __esm({
15574
15641
  }
15575
15642
  });
15576
15643
 
15644
+ // src/cli/uninit.ts
15645
+ var uninit_exports = {};
15646
+ __export(uninit_exports, {
15647
+ findMatchingParen: () => findMatchingParen,
15648
+ isInitCreatedInstrumentation: () => isInitCreatedInstrumentation,
15649
+ processJsonMcpConfig: () => processJsonMcpConfig,
15650
+ processTomlMcpConfig: () => processTomlMcpConfig,
15651
+ removeGlasstraceConfigImport: () => removeGlasstraceConfigImport,
15652
+ removeMarkerSection: () => removeMarkerSection,
15653
+ removeRegisterGlasstrace: () => removeRegisterGlasstrace,
15654
+ runUninit: () => runUninit,
15655
+ unwrapCJSExport: () => unwrapCJSExport,
15656
+ unwrapExport: () => unwrapExport
15657
+ });
15658
+ function findMatchingParen(text, openPos) {
15659
+ let depth = 0;
15660
+ for (let i = openPos; i < text.length; i++) {
15661
+ if (text[i] === "(") {
15662
+ depth++;
15663
+ } else if (text[i] === ")") {
15664
+ depth--;
15665
+ if (depth === 0) {
15666
+ return i;
15667
+ }
15668
+ }
15669
+ }
15670
+ return -1;
15671
+ }
15672
+ function unwrapExport(content) {
15673
+ const pattern = /export\s+default\s+withGlasstraceConfig\s*\(/;
15674
+ const match = pattern.exec(content);
15675
+ if (!match) {
15676
+ return { content, unwrapped: false };
15677
+ }
15678
+ const openParenIdx = match.index + match[0].length - 1;
15679
+ const closeParenIdx = findMatchingParen(content, openParenIdx);
15680
+ if (closeParenIdx === -1) {
15681
+ return { content, unwrapped: false };
15682
+ }
15683
+ const innerExpr = content.slice(openParenIdx + 1, closeParenIdx).trim();
15684
+ if (innerExpr.length === 0) {
15685
+ return { content, unwrapped: false };
15686
+ }
15687
+ const before = content.slice(0, match.index);
15688
+ const afterClose = content.slice(closeParenIdx + 1);
15689
+ const trailing = afterClose.replace(/^;?\s*/, "");
15690
+ const result = before + `export default ${innerExpr};
15691
+ ` + trailing;
15692
+ return { content: result, unwrapped: true };
15693
+ }
15694
+ function unwrapCJSExport(content) {
15695
+ const pattern = /module\.exports\s*=\s*withGlasstraceConfig\s*\(/;
15696
+ const match = pattern.exec(content);
15697
+ if (!match) {
15698
+ return { content, unwrapped: false };
15699
+ }
15700
+ const openParenIdx = match.index + match[0].length - 1;
15701
+ const closeParenIdx = findMatchingParen(content, openParenIdx);
15702
+ if (closeParenIdx === -1) {
15703
+ return { content, unwrapped: false };
15704
+ }
15705
+ const innerExpr = content.slice(openParenIdx + 1, closeParenIdx).trim();
15706
+ if (innerExpr.length === 0) {
15707
+ return { content, unwrapped: false };
15708
+ }
15709
+ const before = content.slice(0, match.index);
15710
+ const afterClose = content.slice(closeParenIdx + 1);
15711
+ const trailing = afterClose.replace(/^;?\s*/, "");
15712
+ const result = before + `module.exports = ${innerExpr};
15713
+ ` + trailing;
15714
+ return { content: result, unwrapped: true };
15715
+ }
15716
+ function removeGlasstraceConfigImport(content) {
15717
+ const esmSoleImport = /import\s*\{\s*withGlasstraceConfig\s*\}\s*from\s*["']@glasstrace\/sdk["']\s*;?\s*\n?/;
15718
+ if (esmSoleImport.test(content)) {
15719
+ return content.replace(esmSoleImport, "");
15720
+ }
15721
+ const esmMultiImport = /import\s*\{([^}]*)\}\s*from\s*["']@glasstrace\/sdk["']/;
15722
+ const multiMatch = esmMultiImport.exec(content);
15723
+ if (multiMatch) {
15724
+ const specifiers = multiMatch[1].split(",").map((s) => s.trim()).filter((s) => s !== "" && s !== "withGlasstraceConfig");
15725
+ if (specifiers.length === 0) {
15726
+ return content.replace(
15727
+ /import\s*\{[^}]*\}\s*from\s*["']@glasstrace\/sdk["']\s*;?\s*\n?/,
15728
+ ""
15729
+ );
15730
+ }
15731
+ const newImport = `import { ${specifiers.join(", ")} } from "@glasstrace/sdk"`;
15732
+ return content.replace(multiMatch[0], newImport);
15733
+ }
15734
+ const cjsSoleRequire = /const\s*\{\s*withGlasstraceConfig\s*\}\s*=\s*require\s*\(\s*["']@glasstrace\/sdk["']\s*\)\s*;?\s*\n?/;
15735
+ if (cjsSoleRequire.test(content)) {
15736
+ return content.replace(cjsSoleRequire, "");
15737
+ }
15738
+ const cjsMultiRequire = /const\s*\{([^}]*)\}\s*=\s*require\s*\(\s*["']@glasstrace\/sdk["']\s*\)/;
15739
+ const cjsMultiMatch = cjsMultiRequire.exec(content);
15740
+ if (cjsMultiMatch) {
15741
+ const specifiers = cjsMultiMatch[1].split(",").map((s) => s.trim()).filter((s) => s !== "" && s !== "withGlasstraceConfig");
15742
+ if (specifiers.length === 0) {
15743
+ return content.replace(
15744
+ /const\s*\{[^}]*\}\s*=\s*require\s*\(\s*["']@glasstrace\/sdk["']\s*\)\s*;?\s*\n?/,
15745
+ ""
15746
+ );
15747
+ }
15748
+ const newRequire = `const { ${specifiers.join(", ")} } = require("@glasstrace/sdk")`;
15749
+ return content.replace(cjsMultiMatch[0], newRequire);
15750
+ }
15751
+ return content;
15752
+ }
15753
+ function cleanLeadingBlankLines(content) {
15754
+ return content.replace(/^\n{2,}/, "\n");
15755
+ }
15756
+ function isInitCreatedInstrumentation(content) {
15757
+ const lines = content.split("\n");
15758
+ const importLines = lines.filter(
15759
+ (l) => /^\s*import\s/.test(l) && !l.trim().startsWith("//")
15760
+ );
15761
+ const nonGlasstraceImports = importLines.filter(
15762
+ (l) => !l.includes("@glasstrace/sdk")
15763
+ );
15764
+ if (nonGlasstraceImports.length > 0) {
15765
+ return false;
15766
+ }
15767
+ const registerFnRegex = /export\s+(?:async\s+)?function\s+register\s*\([^)]*\)\s*\{/;
15768
+ const match = registerFnRegex.exec(content);
15769
+ if (!match) {
15770
+ return false;
15771
+ }
15772
+ const afterBrace = content.slice(match.index + match[0].length);
15773
+ const closingBraceIdx = findMatchingBrace(content, match.index + match[0].length - 1);
15774
+ if (closingBraceIdx === -1) {
15775
+ return false;
15776
+ }
15777
+ const body = afterBrace.slice(0, closingBraceIdx - (match.index + match[0].length));
15778
+ const bodyLines = body.split("\n");
15779
+ const statements = bodyLines.filter((l) => {
15780
+ const trimmed = l.trim();
15781
+ return trimmed !== "" && !trimmed.startsWith("//");
15782
+ });
15783
+ if (statements.length !== 1) {
15784
+ return false;
15785
+ }
15786
+ if (!/^\s*registerGlasstrace\s*\(\s*\)\s*;?\s*$/.test(statements[0])) {
15787
+ return false;
15788
+ }
15789
+ const beforeFn = content.slice(0, match.index);
15790
+ const afterFn = content.slice(closingBraceIdx + 1);
15791
+ const topLevelBefore = beforeFn.split("\n").filter((l) => {
15792
+ const trimmed = l.trim();
15793
+ return trimmed !== "" && !trimmed.startsWith("//") && !trimmed.startsWith("import ") && !trimmed.startsWith("import{");
15794
+ });
15795
+ const topLevelAfter = afterFn.split("\n").filter((l) => {
15796
+ const trimmed = l.trim();
15797
+ return trimmed !== "" && !trimmed.startsWith("//");
15798
+ });
15799
+ return topLevelBefore.length === 0 && topLevelAfter.length === 0;
15800
+ }
15801
+ function findMatchingBrace(text, openPos) {
15802
+ let depth = 0;
15803
+ for (let i = openPos; i < text.length; i++) {
15804
+ if (text[i] === "{") {
15805
+ depth++;
15806
+ } else if (text[i] === "}") {
15807
+ depth--;
15808
+ if (depth === 0) {
15809
+ return i;
15810
+ }
15811
+ }
15812
+ }
15813
+ return -1;
15814
+ }
15815
+ function removeRegisterGlasstrace(content) {
15816
+ let result = content;
15817
+ result = result.replace(
15818
+ /[ \t]*\/\/\s*Glasstrace must be registered[^\n]*\n(?:[ \t]*\/\/[^\n]*\n)*[ \t]*registerGlasstrace\s*\(\s*\)\s*;?\s*\n?/g,
15819
+ ""
15820
+ );
15821
+ result = result.replace(
15822
+ /[ \t]*registerGlasstrace\s*\(\s*\)\s*;?\s*\n?/g,
15823
+ ""
15824
+ );
15825
+ const soleImportPattern = /import\s*\{\s*registerGlasstrace\s*\}\s*from\s*["']@glasstrace\/sdk["']\s*;?\s*\n?/;
15826
+ if (soleImportPattern.test(result)) {
15827
+ result = result.replace(soleImportPattern, "");
15828
+ } else {
15829
+ const multiImportPattern = /import\s*\{([^}]*)\}\s*from\s*["']@glasstrace\/sdk["']/;
15830
+ const multiMatch = multiImportPattern.exec(result);
15831
+ if (multiMatch) {
15832
+ const specifiers = multiMatch[1].split(",").map((s) => s.trim()).filter((s) => s !== "" && s !== "registerGlasstrace");
15833
+ if (specifiers.length === 0) {
15834
+ result = result.replace(
15835
+ /import\s*\{[^}]*\}\s*from\s*["']@glasstrace\/sdk["']\s*;?\s*\n?/,
15836
+ ""
15837
+ );
15838
+ } else {
15839
+ const newImport = `import { ${specifiers.join(", ")} } from "@glasstrace/sdk"`;
15840
+ result = result.replace(multiMatch[0], newImport);
15841
+ }
15842
+ }
15843
+ }
15844
+ return cleanLeadingBlankLines(result);
15845
+ }
15846
+ function removeMarkerSection(content) {
15847
+ const lines = content.split("\n");
15848
+ let startIdx = -1;
15849
+ let endIdx = -1;
15850
+ for (let i = 0; i < lines.length; i++) {
15851
+ const trimmed = lines[i].trim();
15852
+ if (trimmed === "<!-- glasstrace:mcp:start -->" || trimmed === "# glasstrace:mcp:start") {
15853
+ startIdx = i;
15854
+ } else if ((trimmed === "<!-- glasstrace:mcp:end -->" || trimmed === "# glasstrace:mcp:end") && startIdx !== -1) {
15855
+ endIdx = i;
15856
+ break;
15857
+ }
15858
+ }
15859
+ if (startIdx === -1 || endIdx === -1) {
15860
+ return { content, removed: false };
15861
+ }
15862
+ const before = lines.slice(0, startIdx);
15863
+ const after = lines.slice(endIdx + 1);
15864
+ while (before.length > 0 && before[before.length - 1].trim() === "") {
15865
+ before.pop();
15866
+ }
15867
+ const result = [...before, ...after].join("\n");
15868
+ const trimmedResult = result.trimEnd();
15869
+ return {
15870
+ content: trimmedResult.length > 0 ? trimmedResult + "\n" : "",
15871
+ removed: true
15872
+ };
15873
+ }
15874
+ function processJsonMcpConfig(content) {
15875
+ let parsed;
15876
+ try {
15877
+ parsed = JSON.parse(content);
15878
+ } catch {
15879
+ return { action: "skipped" };
15880
+ }
15881
+ const mcpServers = parsed["mcpServers"];
15882
+ if (!mcpServers || typeof mcpServers !== "object" || !("glasstrace" in mcpServers)) {
15883
+ return { action: "skipped" };
15884
+ }
15885
+ const remainingServers = Object.keys(mcpServers).filter((k) => k !== "glasstrace");
15886
+ const otherTopLevelKeys = Object.keys(parsed).filter((k) => k !== "mcpServers");
15887
+ if (remainingServers.length === 0 && otherTopLevelKeys.length === 0) {
15888
+ return { action: "deleted" };
15889
+ }
15890
+ const { glasstrace: _, ...rest } = mcpServers;
15891
+ void _;
15892
+ if (remainingServers.length > 0) {
15893
+ parsed["mcpServers"] = rest;
15894
+ } else {
15895
+ delete parsed["mcpServers"];
15896
+ }
15897
+ return { action: "removed-key", content: JSON.stringify(parsed, null, 2) + "\n" };
15898
+ }
15899
+ function processTomlMcpConfig(content) {
15900
+ if (!content.includes("[mcp_servers.glasstrace]")) {
15901
+ return { action: "skipped" };
15902
+ }
15903
+ const lines = content.split("\n");
15904
+ const startIdx = lines.findIndex(
15905
+ (l) => l.trim() === "[mcp_servers.glasstrace]"
15906
+ );
15907
+ if (startIdx === -1) {
15908
+ return { action: "skipped" };
15909
+ }
15910
+ let endIdx = lines.length;
15911
+ for (let i = startIdx + 1; i < lines.length; i++) {
15912
+ if (/^\s*\[/.test(lines[i])) {
15913
+ endIdx = i;
15914
+ break;
15915
+ }
15916
+ }
15917
+ const before = lines.slice(0, startIdx);
15918
+ const after = lines.slice(endIdx);
15919
+ while (before.length > 0 && before[before.length - 1].trim() === "") {
15920
+ before.pop();
15921
+ }
15922
+ const result = [...before, ...after].join("\n").trimEnd();
15923
+ if (result.trim().length === 0) {
15924
+ return { action: "deleted" };
15925
+ }
15926
+ return { action: "removed-section", content: result + "\n" };
15927
+ }
15928
+ async function runUninit(options) {
15929
+ const { projectRoot, dryRun } = options;
15930
+ const summary = [];
15931
+ const warnings = [];
15932
+ const errors = [];
15933
+ const prefix = dryRun ? "[dry run] " : "";
15934
+ try {
15935
+ let configHandled = false;
15936
+ for (const name of NEXT_CONFIG_NAMES3) {
15937
+ const configPath = path5.join(projectRoot, name);
15938
+ if (!fs5.existsSync(configPath)) {
15939
+ continue;
15940
+ }
15941
+ const content = fs5.readFileSync(configPath, "utf-8");
15942
+ if (!content.includes("withGlasstraceConfig")) {
15943
+ continue;
15944
+ }
15945
+ const isESM = name.endsWith(".ts") || name.endsWith(".mjs");
15946
+ const unwrapResult = isESM ? unwrapExport(content) : unwrapCJSExport(content);
15947
+ if (unwrapResult.unwrapped) {
15948
+ const cleaned = removeGlasstraceConfigImport(unwrapResult.content);
15949
+ const final = cleanLeadingBlankLines(cleaned);
15950
+ if (!dryRun) {
15951
+ fs5.writeFileSync(configPath, final, "utf-8");
15952
+ }
15953
+ summary.push(`${prefix}Unwrapped withGlasstraceConfig from ${name}`);
15954
+ configHandled = true;
15955
+ break;
15956
+ } else {
15957
+ warnings.push(
15958
+ `${name} contains withGlasstraceConfig but could not be automatically unwrapped. Please remove withGlasstraceConfig() manually.`
15959
+ );
15960
+ configHandled = true;
15961
+ break;
15962
+ }
15963
+ }
15964
+ if (!configHandled) {
15965
+ }
15966
+ } catch (err) {
15967
+ errors.push(
15968
+ `Failed to process next.config: ${err instanceof Error ? err.message : String(err)}`
15969
+ );
15970
+ }
15971
+ try {
15972
+ const instrPath = path5.join(projectRoot, "instrumentation.ts");
15973
+ if (fs5.existsSync(instrPath)) {
15974
+ const content = fs5.readFileSync(instrPath, "utf-8");
15975
+ if (content.includes("registerGlasstrace") || content.includes("@glasstrace/sdk")) {
15976
+ if (isInitCreatedInstrumentation(content)) {
15977
+ if (!dryRun) {
15978
+ fs5.unlinkSync(instrPath);
15979
+ }
15980
+ summary.push(`${prefix}Deleted instrumentation.ts (init-created)`);
15981
+ } else {
15982
+ const cleaned = removeRegisterGlasstrace(content);
15983
+ if (cleaned !== content) {
15984
+ if (!dryRun) {
15985
+ fs5.writeFileSync(instrPath, cleaned, "utf-8");
15986
+ }
15987
+ summary.push(
15988
+ `${prefix}Removed registerGlasstrace() from instrumentation.ts`
15989
+ );
15990
+ }
15991
+ }
15992
+ }
15993
+ }
15994
+ } catch (err) {
15995
+ errors.push(
15996
+ `Failed to process instrumentation.ts: ${err instanceof Error ? err.message : String(err)}`
15997
+ );
15998
+ }
15999
+ try {
16000
+ const glasstraceDir = path5.join(projectRoot, ".glasstrace");
16001
+ if (fs5.existsSync(glasstraceDir)) {
16002
+ if (!dryRun) {
16003
+ fs5.rmSync(glasstraceDir, { recursive: true, force: true });
16004
+ }
16005
+ summary.push(`${prefix}Removed .glasstrace/ directory`);
16006
+ }
16007
+ } catch (err) {
16008
+ errors.push(
16009
+ `Failed to remove .glasstrace/: ${err instanceof Error ? err.message : String(err)}`
16010
+ );
16011
+ }
16012
+ try {
16013
+ const envPath = path5.join(projectRoot, ".env.local");
16014
+ if (fs5.existsSync(envPath)) {
16015
+ const content = fs5.readFileSync(envPath, "utf-8");
16016
+ const lines = content.split("\n");
16017
+ const filtered = lines.filter((line) => {
16018
+ const trimmed = line.trim();
16019
+ return !(/^\s*#?\s*GLASSTRACE_API_KEY\s*=/.test(trimmed) || /^\s*#?\s*GLASSTRACE_COVERAGE_MAP\s*=/.test(trimmed));
16020
+ });
16021
+ if (filtered.length !== lines.length) {
16022
+ const result = filtered.join("\n");
16023
+ if (result.trim().length === 0) {
16024
+ if (!dryRun) {
16025
+ fs5.unlinkSync(envPath);
16026
+ }
16027
+ summary.push(`${prefix}Deleted .env.local (no remaining entries)`);
16028
+ } else {
16029
+ if (!dryRun) {
16030
+ fs5.writeFileSync(envPath, result, "utf-8");
16031
+ }
16032
+ summary.push(`${prefix}Removed GLASSTRACE entries from .env.local`);
16033
+ }
16034
+ }
16035
+ }
16036
+ } catch (err) {
16037
+ errors.push(
16038
+ `Failed to process .env.local: ${err instanceof Error ? err.message : String(err)}`
16039
+ );
16040
+ }
16041
+ try {
16042
+ const gitignorePath = path5.join(projectRoot, ".gitignore");
16043
+ if (fs5.existsSync(gitignorePath)) {
16044
+ const content = fs5.readFileSync(gitignorePath, "utf-8");
16045
+ const lines = content.split("\n");
16046
+ const mcpGitignoreEntries = /* @__PURE__ */ new Set([
16047
+ ".glasstrace/",
16048
+ ".mcp.json",
16049
+ ".cursor/mcp.json",
16050
+ ".gemini/settings.json",
16051
+ ".codex/config.toml"
16052
+ ]);
16053
+ const filtered = lines.filter(
16054
+ (line) => !mcpGitignoreEntries.has(line.trim())
16055
+ );
16056
+ if (filtered.length !== lines.length) {
16057
+ const result = filtered.join("\n");
16058
+ if (result.trim().length === 0) {
16059
+ if (!dryRun) {
16060
+ fs5.unlinkSync(gitignorePath);
16061
+ }
16062
+ summary.push(`${prefix}Deleted .gitignore (no remaining entries)`);
16063
+ } else {
16064
+ if (!dryRun) {
16065
+ fs5.writeFileSync(gitignorePath, result, "utf-8");
16066
+ }
16067
+ summary.push(`${prefix}Removed Glasstrace entries from .gitignore`);
16068
+ }
16069
+ }
16070
+ }
16071
+ } catch (err) {
16072
+ errors.push(
16073
+ `Failed to process .gitignore: ${err instanceof Error ? err.message : String(err)}`
16074
+ );
16075
+ }
16076
+ try {
16077
+ for (const configFile of MCP_CONFIG_FILES) {
16078
+ const configPath = path5.join(projectRoot, configFile);
16079
+ if (!fs5.existsSync(configPath)) {
16080
+ continue;
16081
+ }
16082
+ const content = fs5.readFileSync(configPath, "utf-8");
16083
+ const result = processJsonMcpConfig(content);
16084
+ if (result.action === "deleted") {
16085
+ if (!dryRun) {
16086
+ fs5.unlinkSync(configPath);
16087
+ }
16088
+ summary.push(`${prefix}Deleted ${configFile}`);
16089
+ } else if (result.action === "removed-key" && result.content !== void 0) {
16090
+ if (!dryRun) {
16091
+ fs5.writeFileSync(configPath, result.content, "utf-8");
16092
+ }
16093
+ summary.push(`${prefix}Removed glasstrace from ${configFile}`);
16094
+ }
16095
+ }
16096
+ const codexConfigPath = path5.join(projectRoot, ".codex", "config.toml");
16097
+ if (fs5.existsSync(codexConfigPath)) {
16098
+ const content = fs5.readFileSync(codexConfigPath, "utf-8");
16099
+ const tomlResult = processTomlMcpConfig(content);
16100
+ if (tomlResult.action === "deleted") {
16101
+ if (!dryRun) {
16102
+ fs5.unlinkSync(codexConfigPath);
16103
+ }
16104
+ summary.push(`${prefix}Deleted .codex/config.toml`);
16105
+ } else if (tomlResult.action === "removed-section" && tomlResult.content !== void 0) {
16106
+ if (!dryRun) {
16107
+ fs5.writeFileSync(codexConfigPath, tomlResult.content, "utf-8");
16108
+ }
16109
+ summary.push(`${prefix}Removed glasstrace from .codex/config.toml`);
16110
+ }
16111
+ }
16112
+ const hasWindsurfMarkers = fs5.existsSync(path5.join(projectRoot, ".windsurfrules")) || fs5.existsSync(path5.join(projectRoot, ".windsurf"));
16113
+ if (hasWindsurfMarkers) {
16114
+ const windsurfConfigPath = path5.join(
16115
+ os.homedir(),
16116
+ ".codeium",
16117
+ "windsurf",
16118
+ "mcp_config.json"
16119
+ );
16120
+ if (fs5.existsSync(windsurfConfigPath)) {
16121
+ const content = fs5.readFileSync(windsurfConfigPath, "utf-8");
16122
+ const windsurfResult = processJsonMcpConfig(content);
16123
+ if (windsurfResult.action === "deleted") {
16124
+ if (!dryRun) {
16125
+ fs5.unlinkSync(windsurfConfigPath);
16126
+ }
16127
+ summary.push(`${prefix}Deleted Windsurf MCP config`);
16128
+ } else if (windsurfResult.action === "removed-key" && windsurfResult.content !== void 0) {
16129
+ if (!dryRun) {
16130
+ fs5.writeFileSync(windsurfConfigPath, windsurfResult.content, "utf-8");
16131
+ }
16132
+ summary.push(`${prefix}Removed glasstrace from Windsurf MCP config`);
16133
+ }
16134
+ }
16135
+ }
16136
+ } catch (err) {
16137
+ errors.push(
16138
+ `Failed to process MCP config: ${err instanceof Error ? err.message : String(err)}`
16139
+ );
16140
+ }
16141
+ try {
16142
+ for (const infoFile of AGENT_INFO_FILES) {
16143
+ const filePath = path5.join(projectRoot, infoFile);
16144
+ if (!fs5.existsSync(filePath)) {
16145
+ continue;
16146
+ }
16147
+ const content = fs5.readFileSync(filePath, "utf-8");
16148
+ const result = removeMarkerSection(content);
16149
+ if (result.removed) {
16150
+ if (result.content.trim().length === 0) {
16151
+ if (!dryRun) {
16152
+ fs5.unlinkSync(filePath);
16153
+ }
16154
+ summary.push(`${prefix}Deleted ${infoFile} (only contained Glasstrace section)`);
16155
+ } else {
16156
+ if (!dryRun) {
16157
+ fs5.writeFileSync(filePath, result.content, "utf-8");
16158
+ }
16159
+ summary.push(`${prefix}Removed Glasstrace section from ${infoFile}`);
16160
+ }
16161
+ }
16162
+ }
16163
+ } catch (err) {
16164
+ errors.push(
16165
+ `Failed to process agent info files: ${err instanceof Error ? err.message : String(err)}`
16166
+ );
16167
+ }
16168
+ if (summary.length === 0 && errors.length === 0) {
16169
+ summary.push("No Glasstrace artifacts found \u2014 nothing to do.");
16170
+ }
16171
+ return { exitCode: errors.length > 0 ? 1 : 0, summary, warnings, errors };
16172
+ }
16173
+ var fs5, os, path5, NEXT_CONFIG_NAMES3, MCP_CONFIG_FILES, AGENT_INFO_FILES;
16174
+ var init_uninit = __esm({
16175
+ "src/cli/uninit.ts"() {
16176
+ "use strict";
16177
+ fs5 = __toESM(require("fs"), 1);
16178
+ os = __toESM(require("os"), 1);
16179
+ path5 = __toESM(require("path"), 1);
16180
+ NEXT_CONFIG_NAMES3 = ["next.config.ts", "next.config.js", "next.config.mjs"];
16181
+ MCP_CONFIG_FILES = [".mcp.json", ".cursor/mcp.json", ".gemini/settings.json"];
16182
+ AGENT_INFO_FILES = [
16183
+ "CLAUDE.md",
16184
+ "codex.md",
16185
+ ".cursorrules"
16186
+ ];
16187
+ }
16188
+ });
16189
+
15577
16190
  // src/cli/init.ts
15578
16191
  var init_exports = {};
15579
16192
  __export(init_exports, {
@@ -15581,8 +16194,8 @@ __export(init_exports, {
15581
16194
  runInit: () => runInit
15582
16195
  });
15583
16196
  module.exports = __toCommonJS(init_exports);
15584
- var fs4 = __toESM(require("fs"), 1);
15585
- var path4 = __toESM(require("path"), 1);
16197
+ var fs6 = __toESM(require("fs"), 1);
16198
+ var path6 = __toESM(require("path"), 1);
15586
16199
  var readline = __toESM(require("readline"), 1);
15587
16200
  init_scaffolder();
15588
16201
 
@@ -15761,6 +16374,240 @@ init_detect();
15761
16374
  init_configs();
15762
16375
  init_inject();
15763
16376
  init_constants();
16377
+
16378
+ // src/cli/monorepo.ts
16379
+ var fs3 = __toESM(require("fs"), 1);
16380
+ var path3 = __toESM(require("path"), 1);
16381
+ var NEXT_CONFIG_NAMES2 = ["next.config.ts", "next.config.js", "next.config.mjs"];
16382
+ function resolveProjectRoot(cwd) {
16383
+ if (hasNextConfig(cwd)) {
16384
+ return { projectRoot: cwd, isMonorepo: false };
16385
+ }
16386
+ if (hasNextDependency(cwd)) {
16387
+ return { projectRoot: cwd, isMonorepo: false };
16388
+ }
16389
+ if (isMonorepoRoot(cwd)) {
16390
+ const apps = findNextJsApps(cwd);
16391
+ if (apps.length === 0) {
16392
+ throw new Error(
16393
+ "This is a monorepo but no Next.js apps were found in workspace packages."
16394
+ );
16395
+ }
16396
+ if (apps.length === 1) {
16397
+ const appDir = apps[0];
16398
+ const relativePath = path3.relative(cwd, appDir);
16399
+ return {
16400
+ projectRoot: appDir,
16401
+ isMonorepo: true,
16402
+ appRelativePath: relativePath
16403
+ };
16404
+ }
16405
+ const appList = apps.map((app) => ` - ${path3.relative(cwd, app)}`).join("\n");
16406
+ throw new Error(
16407
+ `Found multiple Next.js apps:
16408
+ ${appList}
16409
+ Run init from the specific app directory you want to instrument.`
16410
+ );
16411
+ }
16412
+ throw new Error(
16413
+ "No Next.js project found in the current directory.\nRun this command from your Next.js app directory, or from a monorepo root."
16414
+ );
16415
+ }
16416
+ function hasNextConfig(dir) {
16417
+ return NEXT_CONFIG_NAMES2.some(
16418
+ (name) => fs3.existsSync(path3.join(dir, name))
16419
+ );
16420
+ }
16421
+ function hasNextDependency(dir) {
16422
+ const packageJsonPath = path3.join(dir, "package.json");
16423
+ if (!fs3.existsSync(packageJsonPath)) return false;
16424
+ try {
16425
+ const content = fs3.readFileSync(packageJsonPath, "utf-8");
16426
+ const pkg = JSON.parse(content);
16427
+ const deps = pkg["dependencies"];
16428
+ const devDeps = pkg["devDependencies"];
16429
+ if (typeof deps === "object" && deps !== null && "next" in deps) return true;
16430
+ if (typeof devDeps === "object" && devDeps !== null && "next" in devDeps) return true;
16431
+ } catch {
16432
+ }
16433
+ return false;
16434
+ }
16435
+ function isMonorepoRoot(dir) {
16436
+ if (fs3.existsSync(path3.join(dir, "pnpm-workspace.yaml"))) return true;
16437
+ if (fs3.existsSync(path3.join(dir, "turbo.json"))) return true;
16438
+ if (fs3.existsSync(path3.join(dir, "lerna.json"))) return true;
16439
+ const packageJsonPath = path3.join(dir, "package.json");
16440
+ if (fs3.existsSync(packageJsonPath)) {
16441
+ try {
16442
+ const content = fs3.readFileSync(packageJsonPath, "utf-8");
16443
+ const pkg = JSON.parse(content);
16444
+ if (pkg["workspaces"] !== void 0) return true;
16445
+ } catch {
16446
+ }
16447
+ }
16448
+ return false;
16449
+ }
16450
+ function findNextJsApps(monorepoRoot) {
16451
+ const { includeGlobs, negationPatterns } = collectWorkspaceGlobs(monorepoRoot);
16452
+ if (includeGlobs.length === 0) {
16453
+ throw new Error(
16454
+ 'Monorepo detected but no workspace configuration found.\nAdd a "workspaces" field to package.json or create pnpm-workspace.yaml.'
16455
+ );
16456
+ }
16457
+ const workspaceDirs = expandGlobs(monorepoRoot, includeGlobs);
16458
+ const excludedDirs = expandGlobs(monorepoRoot, negationPatterns);
16459
+ const excludedSet = new Set(excludedDirs);
16460
+ const seen = /* @__PURE__ */ new Set();
16461
+ const nextApps = [];
16462
+ for (const dir of workspaceDirs) {
16463
+ if (seen.has(dir)) continue;
16464
+ seen.add(dir);
16465
+ if (excludedSet.has(dir)) continue;
16466
+ if (hasNextConfig(dir) || hasNextDependency(dir)) {
16467
+ nextApps.push(dir);
16468
+ }
16469
+ }
16470
+ return nextApps.sort();
16471
+ }
16472
+ function collectWorkspaceGlobs(root) {
16473
+ const globs = [];
16474
+ const negations = [];
16475
+ const pnpmPath = path3.join(root, "pnpm-workspace.yaml");
16476
+ if (fs3.existsSync(pnpmPath)) {
16477
+ const content = fs3.readFileSync(pnpmPath, "utf-8");
16478
+ const parsed = parsePnpmWorkspaceYaml(content);
16479
+ globs.push(...parsed.includeGlobs);
16480
+ negations.push(...parsed.negationPatterns);
16481
+ }
16482
+ const packageJsonPath = path3.join(root, "package.json");
16483
+ if (fs3.existsSync(packageJsonPath)) {
16484
+ try {
16485
+ const content = fs3.readFileSync(packageJsonPath, "utf-8");
16486
+ const pkg = JSON.parse(content);
16487
+ globs.push(...parsePackageJsonWorkspaces(pkg));
16488
+ } catch {
16489
+ }
16490
+ }
16491
+ const lernaPath = path3.join(root, "lerna.json");
16492
+ if (fs3.existsSync(lernaPath)) {
16493
+ try {
16494
+ const content = fs3.readFileSync(lernaPath, "utf-8");
16495
+ const lerna = JSON.parse(content);
16496
+ const packages = lerna["packages"];
16497
+ if (Array.isArray(packages)) {
16498
+ for (const pkg of packages) {
16499
+ if (typeof pkg === "string") {
16500
+ globs.push(pkg);
16501
+ }
16502
+ }
16503
+ }
16504
+ } catch {
16505
+ }
16506
+ }
16507
+ return {
16508
+ includeGlobs: [...new Set(globs)],
16509
+ negationPatterns: [...new Set(negations)]
16510
+ };
16511
+ }
16512
+ function parsePnpmWorkspaceYaml(content) {
16513
+ const lines = content.split("\n");
16514
+ const includeGlobs = [];
16515
+ const negationPatterns = [];
16516
+ let inPackages = false;
16517
+ for (const rawLine of lines) {
16518
+ const trimmed = rawLine.trim();
16519
+ if (/^packages\s*:/.test(trimmed)) {
16520
+ inPackages = true;
16521
+ continue;
16522
+ }
16523
+ if (inPackages && trimmed.length > 0 && !trimmed.startsWith("-") && !rawLine.startsWith(" ") && !rawLine.startsWith(" ")) {
16524
+ inPackages = false;
16525
+ continue;
16526
+ }
16527
+ if (!inPackages) continue;
16528
+ const itemMatch = /^\s*-\s+(.+)$/.exec(rawLine);
16529
+ if (!itemMatch) continue;
16530
+ const value = itemMatch[1].trim().replace(/^["']|["']$/g, "");
16531
+ if (value.length === 0) continue;
16532
+ if (value.startsWith("!")) {
16533
+ negationPatterns.push(value.slice(1));
16534
+ continue;
16535
+ }
16536
+ includeGlobs.push(value);
16537
+ }
16538
+ return { includeGlobs, negationPatterns };
16539
+ }
16540
+ function parsePackageJsonWorkspaces(pkg) {
16541
+ const workspaces = pkg["workspaces"];
16542
+ if (workspaces === void 0 || workspaces === null) return [];
16543
+ if (Array.isArray(workspaces)) {
16544
+ return workspaces.filter((w) => typeof w === "string");
16545
+ }
16546
+ if (typeof workspaces === "object") {
16547
+ const obj = workspaces;
16548
+ const packages = obj["packages"];
16549
+ if (Array.isArray(packages)) {
16550
+ return packages.filter((p) => typeof p === "string");
16551
+ }
16552
+ }
16553
+ return [];
16554
+ }
16555
+ function expandGlobs(root, globs) {
16556
+ const dirs = [];
16557
+ for (const glob of globs) {
16558
+ const cleanGlob = glob.replace(/\/+$/, "");
16559
+ if (cleanGlob.includes("**")) {
16560
+ const prefix = cleanGlob.split("**")[0].replace(/\/+$/, "");
16561
+ const baseDir = path3.join(root, prefix);
16562
+ if (fs3.existsSync(baseDir)) {
16563
+ dirs.push(...walkDirectories(baseDir));
16564
+ }
16565
+ } else if (cleanGlob.includes("*")) {
16566
+ const parts = cleanGlob.split("*");
16567
+ const baseDir = path3.join(root, parts[0].replace(/\/+$/, ""));
16568
+ const suffix = parts.slice(1).join("*");
16569
+ if (!fs3.existsSync(baseDir)) continue;
16570
+ let entries;
16571
+ try {
16572
+ entries = fs3.readdirSync(baseDir, { withFileTypes: true });
16573
+ } catch {
16574
+ continue;
16575
+ }
16576
+ for (const entry of entries) {
16577
+ if (!entry.isDirectory()) continue;
16578
+ if (suffix && !entry.name.endsWith(suffix)) continue;
16579
+ dirs.push(path3.join(baseDir, entry.name));
16580
+ }
16581
+ } else {
16582
+ const targetDir = path3.join(root, cleanGlob);
16583
+ if (fs3.existsSync(targetDir) && fs3.statSync(targetDir).isDirectory()) {
16584
+ dirs.push(targetDir);
16585
+ }
16586
+ }
16587
+ }
16588
+ return dirs;
16589
+ }
16590
+ function walkDirectories(baseDir) {
16591
+ const result = [];
16592
+ let entries;
16593
+ try {
16594
+ entries = fs3.readdirSync(baseDir, { withFileTypes: true });
16595
+ } catch {
16596
+ return result;
16597
+ }
16598
+ for (const entry of entries) {
16599
+ if (!entry.isDirectory()) continue;
16600
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
16601
+ const fullPath = path3.join(baseDir, entry.name);
16602
+ if (fs3.existsSync(path3.join(fullPath, "package.json"))) {
16603
+ result.push(fullPath);
16604
+ }
16605
+ result.push(...walkDirectories(fullPath));
16606
+ }
16607
+ return result;
16608
+ }
16609
+
16610
+ // src/cli/init.ts
15764
16611
  function meetsNodeVersion(minMajor) {
15765
16612
  const [major] = process.versions.node.split(".").map(Number);
15766
16613
  return major >= minMajor;
@@ -15787,32 +16634,43 @@ async function promptYesNo(question, defaultValue) {
15787
16634
  });
15788
16635
  }
15789
16636
  async function runInit(options) {
15790
- const { projectRoot, yes, coverageMap } = options;
16637
+ const { yes, coverageMap } = options;
15791
16638
  const summary = [];
15792
16639
  const warnings = [];
15793
16640
  const errors = [];
15794
- const packageJsonPath = path4.join(projectRoot, "package.json");
15795
- if (!fs4.existsSync(packageJsonPath)) {
15796
- errors.push("No package.json found. Run this command from a Node.js project root.");
16641
+ let projectRoot;
16642
+ try {
16643
+ const classification = resolveProjectRoot(options.projectRoot);
16644
+ projectRoot = classification.projectRoot;
16645
+ if (classification.isMonorepo && classification.appRelativePath) {
16646
+ summary.push(`Found Next.js app at ${classification.appRelativePath} \u2014 installing there`);
16647
+ }
16648
+ } catch (err) {
16649
+ errors.push(err instanceof Error ? err.message : String(err));
15797
16650
  return { exitCode: 1, summary, warnings, errors };
15798
16651
  }
15799
- const instrumentationPath = path4.join(projectRoot, "instrumentation.ts");
15800
- const instrumentationExists = fs4.existsSync(instrumentationPath);
15801
- let shouldWriteInstrumentation = true;
15802
- if (instrumentationExists && !yes) {
15803
- shouldWriteInstrumentation = await promptYesNo(
15804
- "instrumentation.ts already exists. Overwrite?",
15805
- false
15806
- );
15807
- } else if (instrumentationExists && yes) {
15808
- shouldWriteInstrumentation = false;
16652
+ const packageJsonPath = path6.join(projectRoot, "package.json");
16653
+ if (!fs6.existsSync(packageJsonPath)) {
16654
+ errors.push("No package.json found. Run this command from a Node.js project root.");
16655
+ return { exitCode: 1, summary, warnings, errors };
15809
16656
  }
15810
16657
  try {
15811
- const created = await scaffoldInstrumentation(projectRoot, shouldWriteInstrumentation && instrumentationExists);
15812
- if (created) {
15813
- summary.push("Created instrumentation.ts");
15814
- } else if (instrumentationExists) {
15815
- summary.push("Skipped instrumentation.ts (already exists)");
16658
+ const instrResult = await scaffoldInstrumentation(projectRoot);
16659
+ switch (instrResult.action) {
16660
+ case "created":
16661
+ summary.push("Created instrumentation.ts");
16662
+ break;
16663
+ case "injected":
16664
+ summary.push("Added registerGlasstrace() to existing instrumentation.ts");
16665
+ break;
16666
+ case "already-registered":
16667
+ summary.push("Skipped instrumentation.ts (registerGlasstrace already present)");
16668
+ break;
16669
+ case "unrecognized":
16670
+ warnings.push(
16671
+ 'instrumentation.ts exists but has no recognizable register() function.\nAdd this import at the top of your file:\n\n import { registerGlasstrace } from "@glasstrace/sdk";\n\nThen add this as the first statement in your register() function:\n\n registerGlasstrace();\n'
16672
+ );
16673
+ break;
15816
16674
  }
15817
16675
  } catch (err) {
15818
16676
  errors.push(`Failed to write instrumentation.ts: ${err instanceof Error ? err.message : String(err)}`);
@@ -15865,14 +16723,14 @@ async function runInit(options) {
15865
16723
  if (isCI) {
15866
16724
  const genericAgent = {
15867
16725
  name: "generic",
15868
- mcpConfigPath: path4.join(projectRoot, ".glasstrace", "mcp.json"),
16726
+ mcpConfigPath: path6.join(projectRoot, ".glasstrace", "mcp.json"),
15869
16727
  infoFilePath: null,
15870
16728
  cliAvailable: false,
15871
16729
  registrationCommand: null
15872
16730
  };
15873
16731
  const genericConfig = generateMcpConfig(genericAgent, MCP_ENDPOINT, anonKey);
15874
16732
  await writeMcpConfig(genericAgent, genericConfig, projectRoot);
15875
- if (genericAgent.mcpConfigPath !== null && fs4.existsSync(genericAgent.mcpConfigPath)) {
16733
+ if (genericAgent.mcpConfigPath !== null && fs6.existsSync(genericAgent.mcpConfigPath)) {
15876
16734
  anyConfigWritten = true;
15877
16735
  summary.push("Created .glasstrace/mcp.json (CI mode)");
15878
16736
  }
@@ -15886,14 +16744,14 @@ async function runInit(options) {
15886
16744
  );
15887
16745
  const genericAgent = {
15888
16746
  name: "generic",
15889
- mcpConfigPath: path4.join(projectRoot, ".glasstrace", "mcp.json"),
16747
+ mcpConfigPath: path6.join(projectRoot, ".glasstrace", "mcp.json"),
15890
16748
  infoFilePath: null,
15891
16749
  cliAvailable: false,
15892
16750
  registrationCommand: null
15893
16751
  };
15894
16752
  const genericConfig = generateMcpConfig(genericAgent, MCP_ENDPOINT, anonKey);
15895
16753
  await writeMcpConfig(genericAgent, genericConfig, projectRoot);
15896
- if (genericAgent.mcpConfigPath !== null && fs4.existsSync(genericAgent.mcpConfigPath)) {
16754
+ if (genericAgent.mcpConfigPath !== null && fs6.existsSync(genericAgent.mcpConfigPath)) {
15897
16755
  anyConfigWritten = true;
15898
16756
  }
15899
16757
  agents = [];
@@ -15903,7 +16761,7 @@ async function runInit(options) {
15903
16761
  try {
15904
16762
  const configContent = generateMcpConfig(agent, MCP_ENDPOINT, anonKey);
15905
16763
  await writeMcpConfig(agent, configContent, projectRoot);
15906
- const configExists = agent.mcpConfigPath !== null && fs4.existsSync(agent.mcpConfigPath);
16764
+ const configExists = agent.mcpConfigPath !== null && fs6.existsSync(agent.mcpConfigPath);
15907
16765
  if (!configExists) {
15908
16766
  continue;
15909
16767
  }
@@ -15990,7 +16848,7 @@ function parseArgs(argv) {
15990
16848
  };
15991
16849
  }
15992
16850
  var scriptPath = typeof process !== "undefined" && process.argv[1] !== void 0 ? process.argv[1].replace(/\\/g, "/") : void 0;
15993
- var scriptBasename = scriptPath !== void 0 ? path4.basename(scriptPath) : void 0;
16851
+ var scriptBasename = scriptPath !== void 0 ? path6.basename(scriptPath) : void 0;
15994
16852
  var isDirectExecution = scriptPath !== void 0 && (scriptPath.endsWith("/cli/init.js") || scriptPath.endsWith("/cli/init.ts") || scriptBasename === "glasstrace");
15995
16853
  if (isDirectExecution) {
15996
16854
  if (!meetsNodeVersion(20)) {
@@ -16061,6 +16919,38 @@ Usage: glasstrace mcp add [--force] [--dry-run]
16061
16919
  }).catch((err) => {
16062
16920
  process.stderr.write(
16063
16921
  `Fatal error: ${err instanceof Error ? err.message : String(err)}
16922
+ `
16923
+ );
16924
+ process.exit(1);
16925
+ });
16926
+ } else if (subcommand === "uninit") {
16927
+ const remainingArgs = process.argv.slice(3);
16928
+ const dryRun = remainingArgs.includes("--dry-run");
16929
+ Promise.resolve().then(() => (init_uninit(), uninit_exports)).then(({ runUninit: runUninit2 }) => runUninit2({ projectRoot: process.cwd(), dryRun })).then((result) => {
16930
+ if (result.errors.length > 0) {
16931
+ for (const err of result.errors) {
16932
+ process.stderr.write(`Error: ${err}
16933
+ `);
16934
+ }
16935
+ }
16936
+ if (result.warnings.length > 0) {
16937
+ for (const warn of result.warnings) {
16938
+ process.stderr.write(`Warning: ${warn}
16939
+ `);
16940
+ }
16941
+ }
16942
+ if (result.summary.length > 0) {
16943
+ process.stderr.write("\n");
16944
+ for (const line of result.summary) {
16945
+ process.stderr.write(` ${line}
16946
+ `);
16947
+ }
16948
+ process.stderr.write("\n");
16949
+ }
16950
+ process.exit(result.exitCode);
16951
+ }).catch((err) => {
16952
+ process.stderr.write(
16953
+ `Fatal error: ${err instanceof Error ? err.message : String(err)}
16064
16954
  `
16065
16955
  );
16066
16956
  process.exit(1);
@@ -16071,6 +16961,7 @@ Usage: glasstrace mcp add [--force] [--dry-run]
16071
16961
 
16072
16962
  Usage:
16073
16963
  glasstrace init [--yes] [--coverage-map]
16964
+ glasstrace uninit [--dry-run]
16074
16965
  glasstrace mcp add [--force] [--dry-run]
16075
16966
  `
16076
16967
  );